import {
  exchangeAuthCode,
  generateAuthUrl,
  frameworkSettings,
  getUserProfile,
  storageSettings,
  checkAuth,
  base64UrlEncode,
  base64UrlDecode,
  PromptTypes,
  StorageKeys,
  IssuerRouteTypes,
  getActiveStorage,
  Permissions,
  refreshToken,
  PermissionAccess,
  UserProfile,
  LoginMethodParams,
  LoginOptions,
  getClaims,
  getClaim,
  getCurrentOrganization,
  getFlag,
  getPermission,
  getPermissions,
  getUserOrganizations,
  getRoles,
  generatePortalUrl,
  Role,
  GeneratePortalUrlParams,
  navigateToKinde,
  setActiveStorage,
  isAuthenticated,
  updateActivityTimestamp,
  switchOrg,
  OrgCode,
} from "@kinde/js-utils";
import * as storeState from "./store";
import React, {
  useCallback,
  useEffect,
  useMemo,
  useState,
  useRef,
} from "react";
import { KindeContext, KindeContextProps } from "./KindeContext";
import { getRedirectUrl } from "../utils/getRedirectUrl";
import packageJson from "../../package.json";
import {
  ErrorProps,
  LogoutOptions,
  PopupOptions,
  ActivityTimeoutConfig,
  TimeoutActivityType,
} from "./types";
import type {
  RefreshTokenResult,
  Scopes,
  SessionManager,
  TimeoutTokenData,
} from "@kinde/js-utils";
// TODO: need to look for old token store and convert.
storageSettings.keyPrefix = "";

const defaultOnRedirectCallback = () => {
  window.history.replaceState({}, document.title, window.location.pathname);
};

enum AuthEvent {
  login = "login",
  logout = "logout",
  register = "register",
  tokenRefreshed = "tokenRefreshed",
}

type EventTypes = {
  (
    event: AuthEvent.tokenRefreshed,
    state: RefreshTokenResult,
    context: KindeContextProps,
  ): void;
  (
    event: AuthEvent,
    state: Record<string, unknown>,
    context: KindeContextProps,
  ): void;
};

type KindeCallbacks = {
  onSuccess?: (
    user: UserProfile,
    state: Record<string, unknown>,
    context: KindeContextProps,
  ) => void;
  onError?: (
    props: ErrorProps,
    state: Record<string, string>,
    context: KindeContextProps,
  ) => void;
  onEvent?: EventTypes;
};

type KindeProviderProps = {
  audience?: string;
  children: React.ReactNode;
  clientId: string;
  domain: string;
  /**
   * Use localstorage for refresh token.
   *
   * Note: This is not recommended for production use, as it is less secure.  Use custom domain and refresh token will have handled without localStorage automatically
   */
  useInsecureForRefreshToken?: boolean;
  logoutUri?: string;
  redirectUri: string;
  callbacks?: KindeCallbacks;
  scope?: string;
  /**
   * When true, renders children immediately and actively manages `isLoading`
   * across init, login, logout, and register flows.
   *
   * This allows consumers to:
   * 1. Access the auth context immediately (no empty render)
   * 2. Rely on `isLoading` to show loading states during auth operations
   *
   * When false (default), children only render after initialization completes.
   */
  forceChildrenRender?: boolean;
  /**
   * When the application is shown in an iFrame, auth will open in a popup window.
   * This is the options for the popup window.
   */
  popupOptions?: PopupOptions;
  store?: SessionManager;
  /**
   * Configuration for activity timeout tracking.
   * ⚠️ Must be memoized or defined outside component to prevent effect re-runs.
   */
  activityTimeout?: ActivityTimeoutConfig;
  refreshOnFocus?: boolean;
};

const defaultCallbacks: KindeCallbacks = {
  onSuccess: defaultOnRedirectCallback,
};
type KindeState = { event: AuthEvent };

type StringProperties = {
  [P in string as P extends "kinde" ? never : P]: string;
};

// Combine types to create final state type
type StateWithKinde = StringProperties & {
  kinde: KindeState;
};

type ProviderState = {
  user?: UserProfile;
  isAuthenticated: boolean;
  isLoading: boolean;
};

const isSameOriginOpener = (): boolean => {
  try {
    const opener = window.opener;
    if (!opener || opener.closed) return false;
    return opener.location.origin === window.location.origin;
  } catch {
    return false;
  }
};

type Options = { skipInitial?: boolean };

const useOnLocationChange = (
  run: (loc: Location) => void,
  { skipInitial = false }: Options = {},
) => {
  const initial = useRef(true);

  useEffect(() => {
    const notify = () => {
      if (skipInitial && initial.current) {
        initial.current = false;
        return;
      }
      run(window.location);
    };

    // back/forward
    const onPop = () => notify();
    window.addEventListener("popstate", onPop);
    window.addEventListener("hashchange", onPop);

    // pushState/replaceState don't emit events: patch them
    const origPush = history.pushState;
    const origReplace = history.replaceState;
    if (history.pushState === origPush) {
      history.pushState = function (...args) {
        origPush.apply(this, args);
        notify();
      };
    }
    if (history.replaceState === origReplace) {
      history.replaceState = function (...args) {
        origReplace.apply(this, args);
        notify();
      };
    }

    // Optional: Navigation API (Chromium, evolving support)
    // const nav: any = (window as any).navigation;
    // if (nav?.addEventListener) nav.addEventListener('navigate', notify);

    return () => {
      window.removeEventListener("popstate", onPop);
      window.removeEventListener("hashchange", onPop);
      history.pushState = origPush;
      history.replaceState = origReplace;
      // if (nav?.removeEventListener) nav.removeEventListener('navigate', notify);
    };
  }, [run, skipInitial]);
};

export const KindeProvider = ({
  audience,
  scope,
  clientId,
  children,
  domain,
  useInsecureForRefreshToken = false,
  redirectUri,
  callbacks = {},
  logoutUri,
  forceChildrenRender = false,
  popupOptions = {},
  store = storeState.memoryStorage,
  activityTimeout,
  refreshOnFocus = false,
}: KindeProviderProps) => {
  // Invitation: read invitation_code once on mount (client). No window → safe defaults (SSR/tests).
  const [invitationSnapshot] = useState(() => {
    if (typeof window === "undefined") {
      return {
        invitationCode: null as string | null,
        redirectPending: false,
      };
    }
    const params = new URLSearchParams(window.location.search);
    const invitationCode = params.get("invitation_code");
    return {
      invitationCode,
      redirectPending: params.has("invitation_code"),
    };
  });
  const invitationCodeRef = useRef(invitationSnapshot.invitationCode);
  const [isInvitationRedirectPending, setIsInvitationRedirectPending] =
    useState(() => invitationSnapshot.redirectPending);
  const mergedCallbacks = useMemo(
    () => ({ ...defaultCallbacks, ...callbacks }),
    [callbacks],
  );

  // Track if activity tracking is currently enabled
  const [isActivityTrackingEnabled, setIsActivityTrackingEnabled] =
    useState(false);

  const [state, setState] = useState<ProviderState>({
    user: undefined,
    isAuthenticated: false,
    isLoading: true,
  });

  const [initStarted, setInitStarted] = useState(forceChildrenRender);
  const contextRef = useRef<KindeContextProps | null>(null);

  const setLoading = useCallback(
    (loading: boolean) => {
      if (forceChildrenRender) {
        setState((prev) => ({ ...prev, isLoading: loading }));
      }
    },
    [forceChildrenRender],
  );

  // Callback that only updates activity timestamp when tracking is enabled and user is authenticated
  const handleLocationChange = useCallback(
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    (_loc: Location) => {
      if (
        isActivityTrackingEnabled &&
        activityTimeout &&
        state.isAuthenticated
      ) {
        updateActivityTimestamp();
      }
    },
    [isActivityTrackingEnabled, activityTimeout, state.isAuthenticated],
  );

  // Only track location changes when activity timeout is configured
  useOnLocationChange(
    handleLocationChange,
    activityTimeout ? {} : { skipInitial: true },
  );

  useEffect(() => {
    setActiveStorage(store);

    const enableActivityTracking = () => {
      if (!activityTimeout || isActivityTrackingEnabled) return;
      storageSettings.activityTimeoutMinutes = activityTimeout.timeoutMinutes;
      storageSettings.activityTimeoutPreWarningMinutes =
        activityTimeout.preWarningMinutes;
      storageSettings.onActivityTimeout = async (
        type: TimeoutActivityType,
        tokens?: TimeoutTokenData,
      ) => {
        try {
          if (type === TimeoutActivityType.timeout) {
            const accessToken = tokens?.accessToken;
            const refreshToken = tokens?.refreshToken;

            const revokeToken = async (
              token: string | null | undefined,
              tokenTypeHint: string,
            ) => {
              if (!token) return;
              const response = await fetch(`${domain}/oauth2/revoke`, {
                method: "POST",
                body: `token=${encodeURIComponent(token)}&client_id=${encodeURIComponent(clientId)}&token_type_hint=${tokenTypeHint}`,
                headers: {
                  "Content-Type": "application/x-www-form-urlencoded",
                },
              });
              if (!response.ok) {
                console.warn(
                  `Failed to revoke ${tokenTypeHint}:`,
                  response.status,
                );
              }
            };

            await Promise.allSettled([
              revokeToken(accessToken, "access_token"),
              revokeToken(refreshToken, "refresh_token"),
            ]);
          }
        } catch (error) {
          console.error("Failed to logout:", error);
        } finally {
          activityTimeout.onTimeout?.(type);
        }
      };
      try {
        updateActivityTimestamp();
      } catch (error) {
        console.error("Failed to update activity timestamp:", error);
        return;
      }
      setIsActivityTrackingEnabled(true);
    };

    const disableActivityTracking = () => {
      if (!isActivityTrackingEnabled) return;

      storageSettings.activityTimeoutMinutes = undefined;
      storageSettings.activityTimeoutPreWarningMinutes = undefined;
      storageSettings.onActivityTimeout = undefined;
      setIsActivityTrackingEnabled(false);
    };

    const unsubscribe = store.subscribe(async () => {
      try {
        const [authenticated, user] = await Promise.all([
          isAuthenticated(),
          getUserProfile(),
        ]);

        if (authenticated && user) {
          enableActivityTracking();
          setState((val) => ({ ...val, user, isAuthenticated: true }));
        } else {
          disableActivityTracking();
          setState((val) => ({
            ...val,
            user: undefined,
            isAuthenticated: false,
          }));
        }
      } catch (error) {
        console.error("Store subscription update failed:", error);
        disableActivityTracking();
        setState((val) => ({
          ...val,
          user: undefined,
          isAuthenticated: false,
        }));
      }
    });
    return () => {
      unsubscribe();
      disableActivityTracking();
    };
  }, [store, activityTimeout, isActivityTrackingEnabled]);

  frameworkSettings.framework = "react";
  frameworkSettings.frameworkVersion = React.version;
  frameworkSettings.sdkVersion = packageJson.version;

  storageSettings.useInsecureForRefreshToken = useInsecureForRefreshToken;

  const initRef = useRef(false);
  const redirectInitiatedRef = useRef(false);

  const login = useCallback(
    async (
      options: LoginMethodParams & { state?: Record<string, string> } = {},
    ) => {
      setLoading(true);
      const optionsState: Record<string, string> = options.state || {};

      options.state = undefined;

      const authProps: LoginOptions = {
        audience,
        clientId,
        ...options,
        supportsReauth: true,
        scope: scope?.split(" ") as Scopes[],
        state: base64UrlEncode(
          JSON.stringify({
            kinde: { event: AuthEvent.login },
            ...optionsState,
          }),
        ),
        redirectURL: getRedirectUrl(options.redirectURL || redirectUri),
      };

      const authUrl = await generateAuthUrl(
        domain,
        IssuerRouteTypes.login,
        authProps,
      );

      try {
        navigateToKinde({
          url: authUrl.url.toString(),
          popupOptions,
          handleResult: processAuthResult,
        });
      } catch (error) {
        setLoading(false);
        if (!contextRef.current) {
          console.error("Login error (context unavailable):", error);
          return;
        }
        mergedCallbacks.onError?.(
          {
            error: "ERR_POPUP",
            errorDescription: (error as Error).message,
          },
          {},
          contextRef.current,
        );
      }
    },
    [
      audience,
      clientId,
      redirectUri,
      popupOptions,
      mergedCallbacks,
      domain,
      scope,
      setLoading,
    ],
  );

  // Handle invitation_code redirect after mount (login triggers navigation / popup)
  useEffect(() => {
    if (
      isInvitationRedirectPending &&
      invitationCodeRef.current &&
      !redirectInitiatedRef.current
    ) {
      redirectInitiatedRef.current = true;
      login({
        prompt: PromptTypes.create,
        invitationCode: invitationCodeRef.current,
      }).catch((error) => {
        console.error("Error processing invitation code:", error);
        if (!contextRef.current) {
          console.error(
            "Invitation redirect error (context unavailable):",
            error,
          );
        } else {
          mergedCallbacks.onError?.(
            {
              error: "ERR_INVITATION_REDIRECT",
              errorDescription:
                error instanceof Error ? error.message : String(error),
            },
            {},
            contextRef.current,
          );
        }
        redirectInitiatedRef.current = false;
        setIsInvitationRedirectPending(false);
      });
    }
  }, [login, isInvitationRedirectPending, mergedCallbacks]); // Include login to ensure it's ready when it becomes available

  const register = useCallback(
    async (
      options: LoginMethodParams & { state?: Record<string, string> } = {},
    ) => {
      setLoading(true);
      const optionsState: Record<string, string> = options.state || {};

      options.state = undefined;

      const authProps: LoginOptions = {
        ...options,
        state: base64UrlEncode(
          JSON.stringify({
            kinde: { event: AuthEvent.register },
            ...optionsState,
          }),
        ),
        supportsReauth: true,
        audience,
        clientId,
        redirectURL: getRedirectUrl(options?.redirectURL || redirectUri),
        prompt: PromptTypes.create,
      };

      try {
        const authUrl = await generateAuthUrl(
          domain,
          IssuerRouteTypes.register,
          authProps,
        );
        try {
          navigateToKinde({
            url: authUrl.url.toString(),
            popupOptions,
            handleResult: processAuthResult,
          });
        } catch (error) {
          setLoading(false);
          if (!contextRef.current) {
            console.error("Register error (context unavailable):", error);
            return;
          }
          mergedCallbacks.onError?.(
            {
              error: "ERR_POPUP",
              errorDescription: (error as Error).message,
            },
            {},
            contextRef.current,
          );
        }
      } catch (error) {
        setLoading(false);
        console.error("Register error:", error);
        if (!contextRef.current) {
          return;
        }
        mergedCallbacks.onError?.(
          {
            error: "ERR_REGISTER",
            errorDescription: String(error),
          },
          {},
          contextRef.current,
        );
      }
    },
    [
      redirectUri,
      popupOptions,
      mergedCallbacks,
      audience,
      clientId,
      domain,
      setLoading,
    ],
  );

  const logout = useCallback(
    async (options?: string | LogoutOptions) => {
      setLoading(true);
      try {
        const params = new URLSearchParams();

        if (options) {
          if (options && typeof options === "string") {
            params.append("redirect", options);
          } else if (typeof options === "object") {
            if (options.redirectUrl || logoutUri) {
              params.append("redirect", options.redirectUrl || logoutUri || "");
            }
            if (options.allSessions) {
              params.append("all_sessions", String(options.allSessions));
            }
          }
        } else {
          params.append("redirect", logoutUri || "");
        }

        await Promise.all([
          store.removeSessionItem(StorageKeys.idToken),
          store.removeSessionItem(StorageKeys.accessToken),
          store.removeSessionItem(StorageKeys.refreshToken),
          storeState.localStorage.removeSessionItem(StorageKeys.refreshToken),
        ]);

        setState((val) => {
          return { ...val, user: undefined, isAuthenticated: false };
        });

        await storeState.localStorage.setSessionItem(
          storeState.LocalKeys.performingLogout,
          "true",
        );

        try {
          await navigateToKinde({
            url: `${domain}/logout?${params.toString()}`,
            popupOptions,
            handleResult: async () => {
              setLoading(false);
            },
          });
        } catch (error) {
          setLoading(false);
          if (!contextRef.current) {
            console.error("Logout error (context unavailable):", error);
            return;
          }
          mergedCallbacks.onError?.(
            {
              error: "ERR_POPUP",
              errorDescription: (error as Error).message,
            },
            {},
            contextRef.current,
          );
        }
      } catch (error) {
        setLoading(false);
        console.error("Logout error:", error);
        if (!contextRef.current) {
          return;
        }
        mergedCallbacks.onError?.(
          {
            error: "ERR_LOGOUT",
            errorDescription: String(error),
          },
          {},
          contextRef.current,
        );
      }
    },
    [store, popupOptions, mergedCallbacks, logoutUri, domain, setLoading],
  );

  const contextValue = useMemo((): KindeContextProps => {
    return {
      // Internal Methods
      login,
      logout,
      register,

      getIdToken: async (): Promise<string | undefined> => {
        const storage = getActiveStorage();
        return (await storage?.getSessionItem(StorageKeys.idToken)) as string;
      },
      getAccessToken: async (): Promise<string | undefined> => {
        const storage = getActiveStorage();
        return (await storage?.getSessionItem(
          StorageKeys.accessToken,
        )) as string;
      },
      /** @deprecated use `getAccessToken` instead */
      getToken: async (): Promise<string | undefined> => {
        const storage = getActiveStorage();
        return (await storage?.getSessionItem(
          StorageKeys.accessToken,
        )) as string;
      },

      getClaim: async <T, V = string | number | string[]>(
        keyName: keyof T,
        tokenType?: "accessToken" | "idToken",
      ): Promise<{ name: keyof T; value: V } | null> => {
        return getClaim<T, V>(keyName, tokenType);
      },
      getClaims: async <T = undefined,>(
        ...args: Parameters<typeof getClaims>
      ): Promise<T | null> => {
        return getClaims<T>(...args);
      },
      /** @deprecated use `getCurrentOrganization` instead */
      getOrganization: async (): Promise<string | null> => {
        return await getCurrentOrganization();
      },
      getCurrentOrganization: async (): Promise<string | null> => {
        return await getCurrentOrganization();
      },
      getFlag: async <T = string | number | boolean,>(
        name: string,
      ): Promise<T | null> => {
        return await getFlag<T>(name);
      },

      getUserProfile,

      getPermission: async <T = string,>(
        permissionKey: T,
      ): Promise<PermissionAccess> => {
        return await getPermission(permissionKey);
      },

      getPermissions: async <T = string,>(): Promise<Permissions<T>> => {
        return getPermissions<T>();
      },
      getUserOrganizations: async (): Promise<string[] | null> => {
        return await getUserOrganizations();
      },
      getRoles: async (): Promise<Role[]> => {
        return await getRoles();
      },
      generatePortalUrl: async (
        options: Omit<GeneratePortalUrlParams, "domain">,
      ): Promise<{ url: URL }> => {
        return await generatePortalUrl({
          domain,
          returnUrl: options.returnUrl || window.location.href,
          subNav: options.subNav,
        });
      },
      switchOrg: async (orgCode: OrgCode): Promise<void> => {
        const result = await switchOrg({
          domain,
          clientId,
          orgCode,
          redirectURL: redirectUri,
        });
        window.location.href = result.url.toString();
      },
      refreshToken: async (
        ...args: Parameters<typeof refreshToken>
      ): ReturnType<typeof refreshToken> => {
        const result = await refreshToken(...args);
        return result;
      },
      ...state,
    };
  }, [state, login, logout, register, domain, clientId, redirectUri]);

  // Keep contextRef in sync with the latest contextValue
  contextRef.current = contextValue;

  const onRefresh = useCallback(
    (data: RefreshTokenResult): void => {
      if (mergedCallbacks.onEvent) {
        mergedCallbacks.onEvent(AuthEvent.tokenRefreshed, data, contextValue);
      }
      if (!data.success && mergedCallbacks.onError) {
        mergedCallbacks.onError(
          {
            error: "ERR_REFRESH_TOKEN",
            errorDescription: data.error ?? "Token refresh failed",
          },
          {},
          contextValue,
        );
      }
    },
    [mergedCallbacks, contextValue],
  );

  // Function to process authentication result from popup
  const processAuthResult = useCallback(
    async (searchParams: URLSearchParams) => {
      try {
        const rawState = searchParams.get("state") || "";
        let decoded: string;
        try {
          decoded = base64UrlDecode(rawState);
        } catch (decodeError) {
          mergedCallbacks.onError?.(
            {
              error: "ERR_STATE_DECODE",
              errorDescription: `Invalid state parameter: ${String(decodeError)}`,
            },
            {},
            contextValue,
          );
          setState((val) => ({ ...val, isLoading: false }));
          return;
        }
        let returnedState: StateWithKinde;
        let kindeState: KindeState;
        try {
          returnedState = JSON.parse(decoded);
          kindeState = Object.assign(
            returnedState.kinde || { event: PromptTypes.login },
          );
        } catch (error) {
          console.error("Error parsing state:", error);
          mergedCallbacks.onError?.(
            {
              error: "ERR_STATE_PARSE",
              errorDescription: String(error),
            },
            {},
            contextValue,
          );
          returnedState = {} as StateWithKinde;
          kindeState = { event: AuthEvent.login };
        }
        try {
          const codeResponse = await exchangeAuthCode({
            urlParams: searchParams,
            domain,
            clientId,
            redirectURL: getRedirectUrl(redirectUri),
            autoRefresh: true,
            onRefresh,
          });

          if (codeResponse.success) {
            const user = await getUserProfile();
            if (user) {
              setState((val) => ({ ...val, user, isAuthenticated: true }));
              mergedCallbacks.onSuccess?.(
                user,
                {
                  ...returnedState,
                  kinde: undefined,
                },
                contextValue,
              );
              if (mergedCallbacks.onEvent) {
                mergedCallbacks.onEvent(
                  kindeState.event,
                  {
                    ...returnedState,
                    kinde: undefined,
                  },
                  contextValue,
                );
              }
            }
          } else {
            mergedCallbacks.onError?.(
              {
                error: "ERR_CODE_EXCHANGE",
                errorDescription: codeResponse.error,
              },
              returnedState,
              contextValue,
            );
          }
        } catch (error) {
          mergedCallbacks.onError?.(
            {
              error: "ERR_POPUP_AUTH",
              errorDescription: String(error),
            },
            returnedState,
            contextValue,
          );
        } finally {
          // Clear loading state appropriately based on forceChildrenRender
          if (forceChildrenRender) {
            // When forceChildrenRender is true, use setLoading to manage state
            setLoading(false);
          } else {
            // When forceChildrenRender is false, directly update state
            setState((val) => ({ ...val, isLoading: false }));
          }
        }
      } finally {
        // Invitation flow: login() resolves before popup completes; clear so init runs and the provider renders after processAuthResult (success or failure).
        setIsInvitationRedirectPending(false);
      }
    },
    [
      domain,
      clientId,
      redirectUri,
      onRefresh,
      mergedCallbacks,
      contextValue,
      setLoading,
      forceChildrenRender,
      setIsInvitationRedirectPending,
    ],
  );

  const handleFocus = useCallback(() => {
    if (
      document.visibilityState === "visible" &&
      state.isAuthenticated &&
      refreshOnFocus
    ) {
      // `refreshToken` resolves with a `RefreshTokenResult` (it does not reject)
      // on failure, so check the resolved result here. Resolved failures are
      // surfaced through `onError` via `onRefresh`; the `.catch` only guards
      // against an unexpected thrown error.
      refreshToken({ domain, clientId, onRefresh })
        .then((result) => {
          if (!result.success) {
            console.error("Error refreshing token:", result.error);
          }
        })
        .catch((error) => {
          console.error("Error refreshing token:", error);
          mergedCallbacks.onError?.(
            {
              error: "ERR_REFRESH_TOKEN",
              errorDescription:
                error instanceof Error ? error.message : String(error),
            },
            {},
            contextValue,
          );
        });
    }
  }, [
    state.isAuthenticated,
    domain,
    clientId,
    onRefresh,
    refreshOnFocus,
    mergedCallbacks,
    contextValue,
  ]);

  useEffect(() => {
    // remove any existing event listener before adding a new one

    document.removeEventListener("visibilitychange", handleFocus);
    if (refreshOnFocus) {
      document.addEventListener("visibilitychange", handleFocus);
      return () => {
        document.removeEventListener("visibilitychange", handleFocus);
      };
    }
  }, [handleFocus, refreshOnFocus]);

  const init = useCallback(async () => {
    if (initRef.current) return;
    if (forceChildrenRender) {
      setInitStarted(true);
    }
    if (!domain || !clientId || !redirectUri) return;
    try {
      // Skip initialization if redirecting for invitation (handled in useEffect above)
      // ⚠️ Do NOT set initRef.current = true here.
      // The idempotency guard must remain unset so init() can re-run
      // once setIsInvitationRedirectPending(false) triggers a re-render.
      if (isInvitationRedirectPending) {
        return;
      }
      const params = new URLSearchParams(window.location.search);

      try {
        initRef.current = true;
        const checkAuthResult = await checkAuth({ domain, clientId });
        // `checkAuth` resolves (it does not throw) with `{ success: false, error }`
        // when a token refresh fails, so surface that through `onError`.
        if (checkAuthResult && !checkAuthResult.success) {
          mergedCallbacks.onError?.(
            {
              error: "ERR_CHECK_AUTH",
              errorDescription:
                checkAuthResult.error ?? "Authentication check failed",
            },
            {},
            contextValue,
          );
        }
      } catch (err) {
        console.warn("checkAuth failed:", err);
        mergedCallbacks.onError?.(
          {
            error: "ERR_CHECK_AUTH",
            errorDescription: err instanceof Error ? err.message : String(err),
          },
          {},
          contextValue,
        );
      }

      if (params.has("error")) {
        const errorCode = params.get("error");
        if (errorCode?.toLowerCase() === "login_link_expired") {
          const reauthState = params.get("reauth_state");
          if (reauthState) {
            login({ reauthState: reauthState });
          }
          return;
        }
        setState((val: ProviderState) => ({ ...val, isLoading: false }));
        return;
      }

      if (
        (await storeState.localStorage.getSessionItem(
          storeState.LocalKeys.performingLogout,
        )) === "true"
      ) {
        await storeState.localStorage.removeSessionItem(
          storeState.LocalKeys.performingLogout,
        );
        if (isSameOriginOpener()) {
          window.close();
        }
      }

      const currentUrlObject = new URL(window.location.href);
      const redirectUrlObject = new URL(redirectUri);

      const isKindeRedirectUri =
        currentUrlObject.origin === redirectUrlObject.origin &&
        currentUrlObject.pathname === redirectUrlObject.pathname;

      const kindeShouldHandle = isKindeRedirectUri && params.has("code");

      if (kindeShouldHandle) {
        if (isSameOriginOpener()) {
          const searchParams = new URLSearchParams(window.location.search);
          window.opener.postMessage(
            {
              type: "KINDE_AUTH_RESULT",
              result: Object.fromEntries(searchParams.entries()),
            },
            window.location.origin,
          );
          window.close();
          return;
        }
        await processAuthResult(new URLSearchParams(window.location.search));

        return;
      }

      try {
        const user = await getUserProfile();
        if (user) {
          setState((val: ProviderState) => ({
            ...val,
            user,
            isAuthenticated: true,
          }));
        }
      } catch (error) {
        console.warn("Error getting user profile", error);
      } finally {
        setState((val: ProviderState) => ({ ...val, isLoading: false }));
      }
    } finally {
      if (isSameOriginOpener()) {
        window.close();
      }
    }
  }, [
    clientId,
    domain,
    redirectUri,
    mergedCallbacks,
    contextValue,
    onRefresh,
    login,
    processAuthResult,
    isInvitationRedirectPending,
    forceChildrenRender,
  ]);

  useEffect(() => {
    const mounted = { current: true };

    if (mounted.current) {
      init();
    }

    return () => {
      mounted.current = false;
    };
  }, [init]);

  // Don't render children if redirecting for invitation
  if (isInvitationRedirectPending && !forceChildrenRender) {
    return <></>;
  }

  const shouldRenderChildren = forceChildrenRender
    ? initStarted
    : initRef.current;

  return shouldRenderChildren ? (
    <KindeContext.Provider value={contextValue}>
      {children}
    </KindeContext.Provider>
  ) : (
    <></>
  );
};
