import { createContext, useCallback, useContext, useMemo, useRef } from "react";
import { defaultTokenData, fetchToken, parseToken, type TokenData } from "./TokenUtils";

export interface TokenState {
	getToken: () => Promise<string | null | undefined>;
}

const initialState: TokenState = {
	getToken: () => new Promise(() => {})
}

const TokenContext = createContext<TokenState>(initialState);


export default function TokenProvider({
	apiUrl,
	children
}: Readonly<{
	apiUrl: string;
	children: React.ReactNode;
}>){
	const tokenRef = useRef<TokenData>(defaultTokenData);
	const refreshPromise = useRef<Promise<string | null | undefined>>(null);

	const getToken = useCallback(async () => {
		if(refreshPromise.current){
			return refreshPromise.current;
		}

		const { accessToken, expires } = tokenRef.current;

		const isExpired = Date.now() > (expires - 5000);	//Give a 5 second buffer

		if(!accessToken || isExpired){
			refreshPromise.current = (async () => {
				try {
					const rawToken = (await fetchToken(apiUrl)).token;
					const parsedToken = parseToken(rawToken);
					tokenRef.current = parsedToken;
					return rawToken;
				}
				catch(error){
					tokenRef.current = defaultTokenData;
					throw error;
				}
				finally {
					refreshPromise.current = null;
				}
			})();
			return refreshPromise.current;
		}

		return accessToken;
	}, [apiUrl]);

	const value: TokenState = useMemo(() => ({
		getToken
	}), [getToken]);

	return (
		<TokenContext.Provider value={value}>
			{children}
		</TokenContext.Provider>
	);
}


// eslint-disable-next-line react-refresh/only-export-components
export function useToken(){
	const context = useContext(TokenContext);

	if(!context){
		throw new Error("useToken must be called inside a TokenProvider");
	}

	return context;
}
