import { Auth0Provider, useAuth0 } from "@auth0/auth0-react";

import React, { useEffect, useState, ReactNode, ReactElement } from "react";
import { ConvexProvider } from "../react/index.js";

// Duplicate type from CLI, which doesn't export types for users.
// Careful if removing, if this entry point exports no interfaces or classes
// doc generation works differently: the sidebar label cannot be overriden.
/**
 * Type representing authentication configuration, from the authInfo array
 * of a convex.json config file.
 *
 * @public
 */
export interface AuthInfo {
  domain: string;
  applicationID: string;
}

// Until we can import from our own entry points (requires TypeScript 4.7),
// just describe the interface enough to help users pass the right type.
type IConvexReactClient = {
  setAuth(s: string): void;
  clearAuth(): void;
};

/**
 * A wrapper React component which provides a ConvexReactClient authenticated
 * with Auth0.
 *
 * Using this component requires
 * * installing the [Auth0 React SDK](/using/users-and-auth#install-the-auth0-react-sdk),
 * * [setting up an Auth0 app](/using/users-and-auth#getting-started-with-auth0), and
 * * [registering that app](/using/users-and-auth#setting-up-convex-with-your-chosen-identity-provider) with `npx convex auth add`.
 *
 *
 * ```tsx
 * function AppWrapper() {
 *   return (
 *     <ConvexProviderWithAuth0 client={convex} authInfo={authInfo}>
 *       <App/>
 *     </ConvexProviderWithAuth0>
 *   );
 * }
 * ```
 *
 * Using this component instead of [ConvexProvider](/api/modules/react#convexprovider)
 * makes the [useAuth0](https://auth0.com/docs/libraries/auth0-react)
 * hook available in addition to [useQuery](/generated-api/react#usequery),
 * [useMutation](/generated-api/react#usemutation), and
 * [useConvex](/generated-api/react#useconvex).
 *
 * If a user is not logged in, this component renders the loading prop
 * with a fallback of a simple "Log in" button if that prop is not passed in.
 * To write your own login component, use the
 * `loginWithRedirect` property of the return value of `useAuth0()`
 * in the click handler of your login button.
 *
 * ```tsx
 * function YourLoggedOutComponent() {
 *   const { loginWithRedirect } = useAuth0();
 *   return (
 *     <div>
 *       <h1>Please log in :)</h1>
 *       <button onClick={loginWithRedirect}>Log in</button>
 *     </div>
 *   );
 * }
 *
 * <ConvexProviderWithAuth0
 *   client={convex}
 *   authInfo={authInfo}
 *   loggedOut={<YourLoggedOutComponent/>}
 * >
 *   <App/>
 * </ConvexProviderWithAuth0>
 * ```
 *
 * @public
 */
export const ConvexProviderWithAuth0: React.FC<{
  children: ReactNode;
  client: IConvexReactClient;
  authInfo: AuthInfo;
  loading?: ReactElement;
  loggedOut?: ReactElement;
}> = ({ children, client, authInfo, loading, loggedOut }) => {
  if (!authInfo) {
    throw new Error(
      "ConvexProviderWithAuth0 component requires authInfo. " +
        "Run `npx convex auth add` to register your auth provider " +
        "and pass config.authInfo[0] as the authInfo prop."
    );
  }
  let domain = authInfo.domain;
  if (domain.startsWith("https://")) {
    domain = domain.slice(8);
  }
  if (domain.endsWith("/")) {
    domain = domain.slice(0, -1);
  }

  return React.createElement(
    Auth0Provider,
    {
      domain: domain,
      clientId: authInfo.applicationID,
      redirectUri:
        typeof window === "undefined" ? undefined : window.location.origin,
      // allows auth0 to cache the authentication state locally
      cacheLocation: "localstorage",
    },
    React.createElement(
      ConvexProviderUsingAuth0,
      {
        client,
        loading,
        loggedOut,
      },
      children
    )
  );
};

function ConvexProviderUsingAuth0({
  children,
  client,
  loading,
  loggedOut,
}: {
  children?: React.ReactNode;
  client: IConvexReactClient;
  loading?: ReactElement;
  loggedOut?: ReactElement;
}) {
  const { isAuthenticated, isLoading, getIdTokenClaims, loginWithRedirect } =
    useAuth0();
  const [clientAuthed, setClientAuthed] = useState(false);

  // default logged out and loading views
  loggedOut =
    loggedOut ||
    React.createElement("button", { onClick: loginWithRedirect }, "Log in");
  loading = loading || React.createElement(React.Fragment, null, null);

  useEffect(() => {
    async function setAuth() {
      const claims = await getIdTokenClaims();
      const token = claims!.__raw;
      client.setAuth(token);
      setClientAuthed(true);
    }

    if (isAuthenticated) {
      setAuth();
      return () => client.clearAuth();
    }
  }, [isAuthenticated, getIdTokenClaims, isLoading, client]);

  if (isLoading || (isAuthenticated && !clientAuthed)) {
    return loading;
  } else if (!isAuthenticated) {
    return loggedOut;
  }
  return React.createElement(
    ConvexProvider,
    { client: client as any },
    children
  );
}
