/**
 * Authentication and Authorization Utilities
 *
 * This module provides utility functions for handling authentication flows,
 * including redirects to auth SPA, cookie management, and session handling.
 */
/**
 * Check if the current window is inside an iframe
 * @returns {boolean} True if running in an iframe, false otherwise
 */
function isInIframe() {
    if (typeof window === "undefined") {
        return false;
    }
    return (window === null || window === void 0 ? void 0 : window.self) !== (window === null || window === void 0 ? void 0 : window.top);
}
/**
 * Send a message to the parent website (when in iframe)
 * @param payload - The data to send to parent window
 */
function sendMessageToParentWebsite(payload) {
    window.parent.postMessage(payload, "*");
}
/**
 * Delete a cookie with specific name, path, and domain
 * @param name - Cookie name
 * @param path - Cookie path
 * @param domain - Cookie domain
 */
function deleteCookie(name, path, domain) {
    const expires = "expires=Thu, 01 Jan 1970 00:00:00 UTC;";
    const cookieValue = `${name}=;`;
    const pathValue = path ? `path=${path};` : "";
    const domainValue = domain ? `domain=${domain};` : "";
    document.cookie = cookieValue + expires + pathValue + domainValue;
}
/**
 * Get all possible domain parts for cookie deletion
 * @param domain - The domain to split
 * @returns Array of domain parts
 * @example
 * getDomainParts('app.example.com') // returns ['app.example.com', 'example.com', 'com']
 */
function getDomainParts(domain) {
    const parts = domain.split(".");
    const domains = [];
    for (let i = parts.length - 1; i >= 0; i--) {
        domains.push(parts.slice(i).join("."));
    }
    return domains;
}
/**
 * Delete a cookie across all possible domain variations
 * @param name - Cookie name to delete
 * @param childDomain - The current domain
 */
function deleteCookieOnAllDomains(name, childDomain) {
    getDomainParts(childDomain).forEach((domainPart) => {
        deleteCookie(name, "/", domainPart);
        deleteCookie(name, "", domainPart);
    });
}
/**
 * Get the parent domain from a given domain
 * @param domain - The domain to process
 * @returns The parent domain (e.g., 'app.example.com' → '.example.com')
 */
function getParentDomain(domain) {
    if (!domain) {
        return "";
    }
    const parts = domain.split(".");
    return parts.length > 1 ? `.${parts.slice(-2).join(".")}` : domain;
}
/**
 * Set a cookie for authentication with cross-domain support
 * @param name - Cookie name
 * @param value - Cookie value
 * @param days - Number of days until expiration (default: 365)
 */
function setCookieForAuth(name, value, days = 365) {
    const expires = new Date();
    expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000);
    const hostname = window.location.hostname;
    let baseDomain = hostname;
    // Calculate base domain (skip for localhost and IP addresses)
    if (hostname !== "localhost" && !/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) {
        const parts = hostname.split(".");
        if (parts.length > 2) {
            baseDomain = `.${parts.slice(-2).join(".")}`;
        }
    }
    const domainAttr = baseDomain ? `;domain=${baseDomain}` : "";
    document.cookie = `${name}=${encodeURIComponent(value)};expires=${expires.toUTCString()};path=/;SameSite=None;Secure${domainAttr}`;
}
/**
 * Clear all cookies for the current domain
 */
function clearCookies() {
    const cookies = document.cookie.split(";");
    for (let i = 0; i < cookies.length; i++) {
        const cookie = cookies[i];
        const eqPos = cookie.indexOf("=");
        const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
        deleteCookieOnAllDomains(name, window.location.hostname);
        document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;Domain=${getParentDomain(window.location.hostname)}`;
    }
}
/**
 * Get the value of a cookie by name
 * @param name - Cookie name
 * @returns The cookie value or null if not found
 */
function getCookieValue(name) {
    const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
    return match ? decodeURIComponent(match[1]) : null;
}
/**
 * Check if user is currently logged in
 * @param tokenKey - The localStorage key for the auth token (default: 'axd_token')
 * @returns True if logged in, false otherwise
 */
function isLoggedIn(tokenKey = "axd_token") {
    return !!localStorage.getItem(tokenKey);
}
/**
 * Extract platform/tenant key from URL
 * @param url - The URL to parse
 * @param pattern - RegExp pattern to match platform key (default matches /platform/[key]/...)
 * @returns The platform key or null if not found
 */
function getPlatformKey(url, pattern = /\/platform\/([^/]+)/) {
    const match = url.match(pattern);
    return match ? match[1] : null;
}
/**
 * Redirect to authentication SPA for login/logout
 *
 * This function handles the complete authentication flow:
 * - Clears localStorage and cookies on logout
 * - Saves redirect path for post-auth return
 * - Handles iframe scenarios
 * - Syncs logout across multiple SPAs via cookies
 *
 * @param options - Configuration options for the redirect
 *
 * @example
 * ```tsx
 * // Basic usage
 * redirectToAuthSpa({
 *   authUrl: 'https://auth.example.com',
 *   appName: 'mentor',
 * });
 *
 * // With logout
 * redirectToAuthSpa({
 *   authUrl: 'https://auth.example.com',
 *   appName: 'mentor',
 *   logout: true,
 *   platformKey: 'my-tenant',
 * });
 *
 * // With custom redirect
 * redirectToAuthSpa({
 *   authUrl: 'https://auth.example.com',
 *   appName: 'mentor',
 *   redirectTo: '/dashboard',
 *   platformKey: 'my-tenant',
 * });
 * ```
 */
async function redirectToAuthSpa(options) {
    const { redirectTo, platformKey, logout = false, saveRedirect = true, authUrl, appName, queryParams = {
        app: "app",
        redirectTo: "redirect-to",
        tenant: "tenant",
    }, redirectPathStorageKey = "redirect_to", cookieNames = {
        currentTenant: "ibl_current_tenant",
        userData: "ibl_user_data",
        tenant: "ibl_tenant",
        logoutTimestamp: "ibl_logout_timestamp",
        loginTimestamp: "ibl_login_timestamp",
        tenantSwitching: "ibl_tenant_switching",
    }, hasNonExpiredAuthToken, isOffline, preserveTokenKey, authRedirectProxy, isNativeApp, scheme = "iblai-mentor", forceRedirect = false, } = options;
    console.log("[redirectToAuthSpa] starting redirect to auth spa", redirectTo, platformKey, logout, saveRedirect, forceRedirect);
    // Skip if a tenant switch is already in progress
    if (!forceRedirect &&
        cookieNames.tenantSwitching &&
        document.cookie.includes(cookieNames.tenantSwitching)) {
        console.log("[redirectToAuthSpa] Tenant switch in progress, skipping redirect");
        return;
    }
    // Skip if a login occurred after the last logout (login takes precedence)
    // but only if this app actually has a valid auth token
    if (!forceRedirect &&
        hasNonExpiredAuthToken &&
        cookieNames.loginTimestamp &&
        cookieNames.logoutTimestamp) {
        const loginTs = getCookieValue(cookieNames.loginTimestamp);
        const logoutTs = getCookieValue(cookieNames.logoutTimestamp);
        const hasValidToken = hasNonExpiredAuthToken();
        console.log("[redirectToAuthSpa] Login/logout timestamp check", {
            loginTs,
            logoutTs,
            hasValidToken,
            loginAhead: loginTs && logoutTs ? Number(loginTs) > Number(logoutTs) : false,
        });
        if (!forceRedirect &&
            hasValidToken &&
            loginTs &&
            logoutTs &&
            Number(loginTs) > Number(logoutTs)) {
            console.log("[redirectToAuthSpa] Login timestamp is ahead of logout timestamp, skipping redirect", { loginTs, logoutTs });
            return;
        }
    }
    // Skip redirect in offline mode
    if (isOffline === null || isOffline === void 0 ? void 0 : isOffline()) {
        console.log("[redirectToAuthSpa] Skipping redirect - offline mode");
        return;
    }
    // Preserve a token before clearing localStorage if requested
    const preservedToken = preserveTokenKey
        ? window.localStorage.getItem(preserveTokenKey)
        : null;
    console.log("[redirectToAuthSpa] clearing local storage");
    localStorage.clear();
    if (logout || isInIframe()) {
        // Delete authentication cookies for cross-SPA synchronization
        const currentDomain = window.location.hostname;
        if (cookieNames.currentTenant) {
            deleteCookieOnAllDomains(cookieNames.currentTenant, currentDomain);
        }
        if (cookieNames.userData) {
            deleteCookieOnAllDomains(cookieNames.userData, currentDomain);
        }
        if (cookieNames.tenant) {
            deleteCookieOnAllDomains(cookieNames.tenant, currentDomain);
        }
        // Set logout timestamp cookie to trigger logout on other SPAs
        if (cookieNames.logoutTimestamp && !isInIframe()) {
            setCookieForAuth(cookieNames.logoutTimestamp, Date.now().toString());
        }
    }
    // Handle iframe scenario
    if (isInIframe()) {
        console.log("[redirectToAuthSpa]: sending authExpired to parent");
        sendMessageToParentWebsite({ authExpired: true });
        return;
    }
    const redirectPath = redirectTo !== null && redirectTo !== void 0 ? redirectTo : `${window.location.pathname}${window.location.search}`;
    // Never save sso-login routes as redirect paths
    if (!redirectPath.startsWith("/sso-login") &&
        !redirectPath.startsWith("/sso-login-complete") &&
        saveRedirect) {
        window.localStorage.setItem(redirectPathStorageKey, redirectPath);
    }
    const platform = platformKey !== null && platformKey !== void 0 ? platformKey : getPlatformKey(redirectPath);
    const redirectToUrl = window.location.origin;
    let authRedirectUrl = `${authUrl}/login?${queryParams.app}=${appName}`;
    authRedirectUrl += `&${queryParams.redirectTo}=${redirectToUrl}`;
    if (platform) {
        authRedirectUrl += `&${queryParams.tenant}=${platform}`;
    }
    if (logout) {
        authRedirectUrl += "&logout=1";
    }
    if ((isNativeApp === null || isNativeApp === void 0 ? void 0 : isNativeApp()) && scheme) {
        authRedirectUrl += `&scheme=${scheme}`;
    }
    // Small delay for any pending operations
    await new Promise((resolve) => setTimeout(resolve, 100));
    if (isNativeApp === null || isNativeApp === void 0 ? void 0 : isNativeApp()) {
        // On native apps (e.g., Tauri), pass preserved token and navigate directly
        if (preservedToken && preserveTokenKey) {
            authRedirectUrl += `&token=${encodeURIComponent(preservedToken)}`;
            console.log("[redirectToAuthSpa] Added preserved token to auth URL");
        }
        console.log("[redirectToAuthSpa] Native app, navigating to auth URL:", authRedirectUrl);
        window.location.href = authRedirectUrl;
    }
    else if (authRedirectProxy) {
        // Use auth redirect proxy endpoint
        window.location.href = `${authRedirectProxy}?to=${encodeURIComponent(authRedirectUrl)}`;
    }
    else {
        window.location.href = authRedirectUrl;
    }
}
/**
 * Get the URL for joining a tenant (sign up flow)
 * @param authUrl - Auth SPA base URL
 * @param tenantKey - Tenant to join
 * @param redirectUrl - URL to redirect to after joining (defaults to current URL)
 * @returns The join URL
 */
function getAuthSpaJoinUrl(authUrl, tenantKey, redirectUrl) {
    const resolvedTenant = tenantKey || getPlatformKey(window.location.pathname) || "";
    if (!resolvedTenant) {
        return "";
    }
    const targetUrl = redirectUrl !== null && redirectUrl !== void 0 ? redirectUrl : window.location.href;
    const joinUrl = `${authUrl}/join?tenant=${encodeURIComponent(resolvedTenant)}&redirect-to=${encodeURIComponent(targetUrl)}`;
    return joinUrl;
}
/**
 * Redirect to auth SPA's join/signup page for a specific tenant
 * @param authUrl - Auth SPA base URL
 * @param tenantKey - Tenant to join
 * @param redirectUrl - URL to redirect to after joining (defaults to current URL)
 */
function redirectToAuthSpaJoinTenant(authUrl, tenantKey, redirectUrl) {
    const resolvedTenant = tenantKey || getPlatformKey(window.location.pathname) || "";
    if (!resolvedTenant) {
        console.log("[auth-redirect] Missing tenant key for join", {
            tenantKey,
            redirectUrl,
        });
        redirectToAuthSpa({
            authUrl,
            appName: "app", // Will need to be configured
            redirectTo: redirectUrl,
        });
        return;
    }
    const targetUrl = redirectUrl !== null && redirectUrl !== void 0 ? redirectUrl : window.location.href;
    const joinUrl = `${authUrl}/join?tenant=${encodeURIComponent(resolvedTenant)}&redirect-to=${encodeURIComponent(targetUrl)}`;
    window.location.href = joinUrl;
}
/**
 * Handle user logout
 *
 * This function:
 * - Clears localStorage (preserving tenant)
 * - Sets logout timestamp cookie for cross-SPA sync
 * - Clears all cookies
 * - Redirects to auth SPA logout endpoint
 *
 * @param options - Logout configuration options
 *
 * @example
 * ```tsx
 * handleLogout({
 *   authUrl: 'https://auth.example.com',
 *   redirectUrl: 'https://app.example.com',
 * });
 * ```
 */
function handleLogout(options) {
    const { redirectUrl = window.location.origin, authUrl, tenantStorageKey = "tenant", logoutTimestampCookie = "ibl_logout_timestamp", callback, } = options;
    const tenant = window.localStorage.getItem(tenantStorageKey);
    window.localStorage.clear();
    if (tenant) {
        window.localStorage.setItem(tenantStorageKey, tenant);
    }
    // Set logout timestamp cookie to trigger logout on other SPAs
    setCookieForAuth(logoutTimestampCookie, Date.now().toString());
    clearCookies();
    callback === null || callback === void 0 ? void 0 : callback();
    if (!isInIframe()) {
        window.location.href = `${authUrl}/logout?redirect-to=${redirectUrl}${tenant ? "&tenant=" + tenant : ""}`;
    }
}
// ── Tenant-switch coordination ─────────────────────────────────────────────
// Cross-tab tenant switching: a TTL lock (shared via localStorage) plus a
// BroadcastChannel signal let multiple tabs coordinate a switch and prevent the
// "ping-pong" where a stale tab reverts the session to its own route's tenant.
const TENANT_SWITCH_CHANNEL = "ibl-tenant-switch";
const TENANT_SWITCH_LOCK_KEY = "ibl_tenant_switch_lock";
const DEFAULT_TENANT_SWITCH_LOCK_TTL_MS = 8000;
let _tabId = null;
/**
 * Stable per-tab id. Uses sessionStorage so it survives same-tab reloads while
 * staying unique per tab/window.
 */
function getTabId() {
    var _a, _b;
    if (_tabId)
        return _tabId;
    try {
        const existing = sessionStorage.getItem("ibl_tab_id");
        if (existing)
            return (_tabId = existing);
        const id = (_b = (_a = crypto === null || crypto === void 0 ? void 0 : crypto.randomUUID) === null || _a === void 0 ? void 0 : _a.call(crypto)) !== null && _b !== void 0 ? _b : `${Date.now()}-${Math.random()}`;
        sessionStorage.setItem("ibl_tab_id", id);
        return (_tabId = id);
    }
    catch (_c) {
        return (_tabId = `${Date.now()}-${Math.random()}`);
    }
}
/** The active (non-expired) tenant-switch lock, or null. */
function readTenantSwitchLock(ttlMs = DEFAULT_TENANT_SWITCH_LOCK_TTL_MS) {
    try {
        const raw = localStorage.getItem(TENANT_SWITCH_LOCK_KEY);
        if (!raw)
            return null;
        const lock = JSON.parse(raw);
        if (typeof (lock === null || lock === void 0 ? void 0 : lock.ts) !== "number")
            return null;
        if (Date.now() - lock.ts > ttlMs)
            return null;
        return lock;
    }
    catch (_a) {
        return null;
    }
}
/** Write a fresh lock for `tenant` (newest intent). Returns the lock written. */
function writeTenantSwitchLock(tenant) {
    const lock = {
        tenant,
        token: `${Date.now()}-${getTabId()}`,
        ts: Date.now(),
        senderId: getTabId(),
    };
    try {
        localStorage.setItem(TENANT_SWITCH_LOCK_KEY, JSON.stringify(lock));
    }
    catch (_a) { }
    return lock;
}
/** Extend an existing lock's window from "now" (no-op when no active lock). */
function refreshTenantSwitchLock() {
    const lock = readTenantSwitchLock();
    if (!lock)
        return;
    try {
        localStorage.setItem(TENANT_SWITCH_LOCK_KEY, JSON.stringify({ ...lock, ts: Date.now() }));
    }
    catch (_a) { }
}
function clearTenantSwitchLock() {
    try {
        localStorage.removeItem(TENANT_SWITCH_LOCK_KEY);
    }
    catch (_a) { }
}
/** True while a tenant switch is in flight — gate automatic reverts on this. */
function isTenantSwitchInProgress(ttlMs) {
    return readTenantSwitchLock(ttlMs) !== null;
}
/**
 * Switch the active tenant. Coordinates across tabs (TTL lock + BroadcastChannel),
 * clears storage, then redirects through the auth SPA's `/login/complete`.
 *
 * @example
 * await handleTenantSwitch("acme", {
 *   authUrl: config.authUrl(),
 *   clearCurrentTenantCookie,
 * });
 */
async function handleTenantSwitch(tenant, options) {
    var _a, _b, _c;
    const { authUrl, redirectPathStorageKey = "redirect-to", preserveTokenKey = "edx_jwt_token", tenantStorageKey = "tenant", tenantSwitchingCookie = "ibl_tenant_switching", queryParams = {}, saveRedirect = false, redirectUrl, broadcast = true, clearCurrentTenantCookie, } = options;
    const tenantParam = (_a = queryParams.tenant) !== null && _a !== void 0 ? _a : "tenant";
    const redirectToParam = (_b = queryParams.redirectTo) !== null && _b !== void 0 ? _b : "redirect-to";
    // Guard: don't start a new user switch while one is already in progress.
    if (broadcast && document.cookie.includes(tenantSwitchingCookie))
        return;
    // Guard: already on the target tenant.
    if (tenant === localStorage.getItem(tenantStorageKey))
        return;
    if (broadcast) {
        setCookieForAuth(tenantSwitchingCookie, "true");
        writeTenantSwitchLock(tenant);
        if (typeof BroadcastChannel !== "undefined") {
            const channel = new BroadcastChannel(TENANT_SWITCH_CHANNEL);
            const lock = readTenantSwitchLock(options.lockTtlMs);
            channel.postMessage({
                type: "TENANT_SWITCHING",
                tenant,
                senderId: (_c = lock === null || lock === void 0 ? void 0 : lock.senderId) !== null && _c !== void 0 ? _c : getTabId(),
                token: lock === null || lock === void 0 ? void 0 : lock.token,
            });
            channel.close();
        }
        clearCurrentTenantCookie === null || clearCurrentTenantCookie === void 0 ? void 0 : clearCurrentTenantCookie();
    }
    const currentPath = `${window.location.pathname}${window.location.search}`;
    const jwtToken = localStorage.getItem(preserveTokenKey);
    if (broadcast)
        localStorage.clear();
    const params = {
        [tenantParam]: tenant,
        [redirectToParam]: redirectUrl !== null && redirectUrl !== void 0 ? redirectUrl : window.location.origin,
    };
    if (jwtToken)
        params.token = jwtToken;
    localStorage.setItem(tenantStorageKey, tenant);
    if (saveRedirect)
        localStorage.setItem(redirectPathStorageKey, currentPath);
    // Re-persist: localStorage.clear() above wiped the pre-broadcast lock; this
    // copy survives the redirect so stale tabs honor the switch window.
    if (broadcast)
        writeTenantSwitchLock(tenant);
    await new Promise((resolve) => setTimeout(resolve, 100));
    window.location.href = `${authUrl}/login/complete?${new URLSearchParams(params)}`;
}

export { DEFAULT_TENANT_SWITCH_LOCK_TTL_MS, TENANT_SWITCH_CHANNEL, TENANT_SWITCH_LOCK_KEY, clearCookies, clearTenantSwitchLock, deleteCookie, deleteCookieOnAllDomains, getAuthSpaJoinUrl, getCookieValue, getDomainParts, getParentDomain, getPlatformKey, getTabId, handleLogout, handleTenantSwitch, isInIframe, isLoggedIn, isTenantSwitchInProgress, readTenantSwitchLock, redirectToAuthSpa, redirectToAuthSpaJoinTenant, refreshTenantSwitchLock, sendMessageToParentWebsite, setCookieForAuth, writeTenantSwitchLock };
//# sourceMappingURL=index.esm.js.map
