'use client'
import { __awaiter } from '../../../../../_virtual/_tslib.js';
import { useMemo, useState, useCallback } from 'react';
import { ProviderEnum, WaasBackupOptionsEnum } from '@dynamic-labs/sdk-api-core';
import { SocialOAuthErrorCode } from '@dynamic-labs/types';
import { SocialAccountAlreadyExistsError, tracing, PlatformService, isMobile, Oauth2Service, DynamicError } from '@dynamic-labs/utils';
import { getDynamicClient } from '../../../client/client.js';
import { useCaptchaContext } from '../../../context/CaptchaContext/CaptchaContext.js';
import { isCaptchaEnabled } from '../../captcha.js';
import '../../../context/DynamicContext/useDynamicContext/useDynamicContext.js';
import { useInternalDynamicContext } from '../../../context/DynamicContext/useDynamicContext/useInternalDynamicContext/useInternalDynamicContext.js';
import { useErrorContext } from '../../../context/ErrorContext/ErrorContext.js';
import '@dynamic-labs-sdk/client';
import '@dynamic-labs-sdk/client/core';
import '../../../config/ApiEndpoint.js';
import '@dynamic-labs/iconic';
import '@dynamic-labs/wallet-connector-core';
import 'react/jsx-runtime';
import '../../../context/ViewContext/ViewContext.js';
import { logger } from '../../../shared/logger.js';
import '@dynamic-labs/wallet-book';
import '../../constants/colors.js';
import '../../constants/values.js';
import { instrumentAuthLoginFailed } from '../../../shared/utils/functions/instrumentAuthLoginFailed/instrumentAuthLoginFailed.js';
import '../../../shared/consts/index.js';
import { digestSHA256 } from '../../functions/digestSHA256/digestSHA256.js';
import { encodeBase64URL } from '../../functions/encodeBase64URL/encodeBase64URL.js';
import '@dynamic-labs/multi-wallet';
import 'react-international-phone';
import { getOauthLoginUrl } from '../../functions/getOauthLoginUrl/getOauthLoginUrl.js';
import { setSocialStorageFor } from '../../functions/socialStorage/socialStorage.js';
import { getProviderByType } from '../../functions/getProviderByType/getProviderByType.js';
import { consumeNonce } from '../../../store/state/nonce/nonce.js';
import { preconnectDynamicWaasIframe } from '../../functions/preconnectDynamicWaasIframe/preconnectDynamicWaasIframe.js';
import { randomString } from '../../functions/randomString/randomString.js';
import { useApiBaseUrl } from '../../../store/state/dynamicContextProps/dynamicContextProps.js';
import '../../../store/state/primaryWalletId/primaryWalletId.js';
import '../../../store/state/connectedWalletsInfo/connectedWalletsInfo.js';
import '../../functions/getWaasAddressTypeLabel/getWaasAddressTypeLabel.js';
import { dynamicEvents } from '../../../events/dynamicEvents.js';
import { generateSessionKeys } from '../../../client/extension/functions/generateSessionKeys/generateSessionKeys.js';
import { telegramSignIn, signInOAuth, verifyOAuth, telegramVerify, getOAuthResult, initAuth, farcasterSignIn, farcasterVerify } from '../../../data/api/oauth/oauth.js';
import '@dynamic-labs/locale';
import { useEmbeddedWalletSessionKeys } from '../useEmbeddedWalletSessionKeys/useEmbeddedWalletSessionKeys.js';
import { useUserAuth } from '../useUserAuth/useUserAuth.js';
import { connect, watchStatus } from './farcaster/farcaster.js';

const socialProviders = [
    'apple',
    'coinbasesocial',
    'discord',
    'epicgames',
    'facebook',
    'farcaster',
    'github',
    'google',
    'kraken',
    'line',
    'microsoft',
    'shopify',
    'spotify',
    'tiktok',
    'telegram',
    'twitch',
    'twitter',
];
// pkce params are only required for twitter and break if added for other providers, like google
// so we should only add provider that support pkce to this list
const providersRequiringPkce = ['twitter', 'github'];
// Hook to handle common social auth logic (for social linking and social sign in)
const useSocialAuth = ({ onSettled, onError, onFarcasterUrl, }) => {
    var _a, _b;
    const { environmentId, setShowAuthFlow, socialSettings, projectSettings, redirectUrl: defaultRedirectUrl, } = useInternalDynamicContext();
    /**
     * Represents if the SDK will use a popup or redirect for social auth
     */
    const strategy = useMemo(() => (socialSettings === null || socialSettings === void 0 ? void 0 : socialSettings.strategy) || 'redirect', [socialSettings]);
    const { setErrorMessage, setError: setContextError } = useErrorContext();
    const apiBaseUrl = useApiBaseUrl();
    const { initAuth: initAuth$1 } = useUserAuth({
        authMethod: 'social',
    });
    const { generateSessionKey, shouldRegisterSessionKeysOnSignin } = useEmbeddedWalletSessionKeys({
        environmentId,
        projectSettings,
    });
    const [isProcessing, setIsProcessing] = useState(false);
    const [error, setError] = useState();
    const { engageCaptcha } = useCaptchaContext();
    const clearError = useCallback(() => {
        setError(undefined);
        setContextError(undefined);
    }, [setContextError]);
    const onFailed = useCallback((provider, reason, options) => {
        setIsProcessing(false);
        onError === null || onError === void 0 ? void 0 : onError(reason.error);
        onSettled === null || onSettled === void 0 ? void 0 : onSettled();
        if (provider && (options === null || options === void 0 ? void 0 : options.raiseAuthFailure))
            dynamicEvents.emit('authFailure', {
                option: provider,
                provider,
                type: 'social',
            }, reason);
    }, [onError, onSettled]);
    const handleError = useCallback((provider, code, message, options) => {
        const error = { code, message };
        instrumentAuthLoginFailed(error, {
            authOrigin: 'social-auth',
            provider,
        });
        setError(error);
        setErrorMessage(code, error, 'social-auth');
        onFailed(provider, { error }, options);
    }, [onFailed, setErrorMessage]);
    const checkValidProvider = useCallback((provider, authMode) => {
        if (!provider) {
            handleError(provider, SocialOAuthErrorCode.NO_PROVIDER, 'Provider is required', { raiseAuthFailure: authMode === 'signin' });
            return false;
        }
        if (!socialProviders.includes(provider)) {
            handleError(provider, SocialOAuthErrorCode.INVALID_PROVIDER, `Invalid social provider. Valid providers are: ${socialProviders.join(', ')}`, { raiseAuthFailure: authMode === 'signin' });
            return false;
        }
        return true;
    }, [handleError]);
    const linkAccount = useCallback((provider, apiCall) => __awaiter(void 0, void 0, void 0, function* () {
        try {
            const response = yield apiCall();
            if (!response) {
                handleError(provider, SocialOAuthErrorCode.VERIFICATION_ERROR, `Failed to link social account: ${provider}`, { raiseAuthFailure: false });
                return;
            }
            onSettled === null || onSettled === void 0 ? void 0 : onSettled();
        }
        catch (error) {
            if (error instanceof SocialAccountAlreadyExistsError) {
                handleError(provider, SocialOAuthErrorCode.ACCOUNT_ALREADY_LINKED_TO_DIFFERENT_PROFILE, `Social account already linked to a different profile: ${provider}`, { raiseAuthFailure: false });
            }
        }
    }), [handleError, onSettled]);
    const signInAccount = useCallback((provider, apiCall) => __awaiter(void 0, void 0, void 0, function* () {
        yield initAuth$1({
            onError: () => handleError(provider, SocialOAuthErrorCode.SIGNIN_ERROR, `Failed to sign-in with social account: ${provider}`, { raiseAuthFailure: true }),
            onSettled,
            options: { provider },
            verifyFunction: () => apiCall(),
        });
    }), [handleError, initAuth$1, onSettled]);
    const completeConnection = useCallback((_c) => __awaiter(void 0, [_c], void 0, function* ({ authMode, provider, state, authCode, captchaToken, verifier, telegramAuthToken, forceCreateUser = false, ssoProviderId, requestedScopes, }) {
        try {
            if (authMode === 'signin') {
                // Warm the embedded-wallet iframe connection at sign-in start
                preconnectDynamicWaasIframe({ apiBaseUrl, projectSettings });
            }
            let sessionPublicKey = undefined;
            tracing.logEvent('oauth', 'completeConnection', tracing.formatObject({
                authMode,
                provider,
                shouldRegisterSessionKeysOnSignin: shouldRegisterSessionKeysOnSignin(),
            }));
            if (shouldRegisterSessionKeysOnSignin()) {
                const keypair = yield generateSessionKey();
                sessionPublicKey = keypair.publicKey;
                tracing.logEvent('oauth', 'completeConnection key generated', tracing.formatObject({
                    sessionPublicKey,
                }));
            }
            if (authMode === 'signin') {
                yield generateSessionKeys();
                if (provider === ProviderEnum.Telegram && telegramAuthToken) {
                    const apiCall = () => telegramSignIn(environmentId, {
                        captchaToken,
                        forceCreateUser,
                        sessionPublicKey,
                        telegramAuthToken,
                    });
                    return yield signInAccount(provider, apiCall);
                }
                else if (provider === ProviderEnum.Telegram) {
                    const apiCall = () => telegramSignIn(environmentId, {
                        captchaToken,
                        code: authCode,
                        sessionPublicKey,
                        state,
                    });
                    yield signInAccount(provider, apiCall);
                }
                else {
                    const apiCall = () => signInOAuth(environmentId, provider, {
                        captchaToken,
                        code: authCode,
                        codeVerifier: verifier,
                        sessionPublicKey,
                        ssoProviderId,
                        state,
                    });
                    yield signInAccount(provider, apiCall);
                }
            }
            else if (authMode === 'stepup') {
                const apiCall = () => verifyOAuth(environmentId, provider, {
                    code: authCode,
                    codeVerifier: verifier,
                    requestedScopes,
                    state,
                });
                yield linkAccount(provider, apiCall);
            }
            else {
                if (provider === ProviderEnum.Telegram) {
                    const apiCall = () => telegramVerify(environmentId, {
                        captchaToken,
                        code: authCode,
                        state,
                    });
                    yield linkAccount(provider, apiCall);
                }
                else {
                    const apiCall = () => verifyOAuth(environmentId, provider, {
                        code: authCode,
                        codeVerifier: verifier,
                        state,
                    });
                    yield linkAccount(provider, apiCall);
                }
            }
        }
        catch (error) {
            logger.error('Social connection failed with unexpected error:', error);
            onFailed(provider, { error }, { raiseAuthFailure: authMode === 'signin' });
            throw error;
        }
    }), [
        apiBaseUrl,
        environmentId,
        generateSessionKey,
        linkAccount,
        onFailed,
        projectSettings,
        shouldRegisterSessionKeysOnSignin,
        signInAccount,
    ]);
    const connectWithFarcaster = useCallback((authMode_1, _d) => __awaiter(void 0, [authMode_1, _d], void 0, function* (authMode, { captchaToken, redirectUrl }) {
        let dynamicNonce;
        try {
            dynamicNonce = consumeNonce();
        }
        catch (error) {
            handleError(ProviderEnum.Farcaster, SocialOAuthErrorCode.GENERAL_ERROR, 'Missing nonce', { raiseAuthFailure: authMode === 'signin' });
            return;
        }
        const domain = PlatformService.getHost();
        const data = yield connect({
            domain,
            nonce: dynamicNonce,
            redirectUrl: redirectUrl || defaultRedirectUrl,
            siweUri: PlatformService.getOrigin(),
        });
        if (!(data === null || data === void 0 ? void 0 : data.url) || !(data === null || data === void 0 ? void 0 : data.channelToken)) {
            handleError(ProviderEnum.Farcaster, SocialOAuthErrorCode.GENERAL_ERROR, "Farcaster didn't return a valid url.", { raiseAuthFailure: authMode === 'signin' });
            return;
        }
        // If on mobile, we want to redirect to the Warpcast app.
        if (isMobile()) {
            yield PlatformService.openURL(data.url);
        }
        else {
            setShowAuthFlow(true);
            onFarcasterUrl === null || onFarcasterUrl === void 0 ? void 0 : onFarcasterUrl(data.url);
        }
        const statusResponse = yield watchStatus({
            channelToken: data.channelToken,
        });
        if (statusResponse.type === 'cancelled') {
            setIsProcessing(false);
            return;
        }
        if (statusResponse.type === 'timeout') {
            handleError(ProviderEnum.Farcaster, SocialOAuthErrorCode.GENERAL_ERROR, `Farcaster timed out while waiting for connection status. Error: ${statusResponse.error}`, { raiseAuthFailure: authMode === 'signin' });
            return;
        }
        if (!statusResponse.data.message ||
            !statusResponse.data.custody ||
            !statusResponse.data.signature ||
            !statusResponse.data.nonce) {
            handleError(ProviderEnum.Farcaster, SocialOAuthErrorCode.GENERAL_ERROR, `Farcaster returned invalid data. Expected to be defined: message, custody, signature, nonce. Received: ${JSON.stringify(statusResponse.data)}`, { raiseAuthFailure: authMode === 'signin' });
            return;
        }
        const { custody: address, message, signature, nonce, } = statusResponse.data;
        if (authMode === 'signin') {
            let sessionPublicKey = undefined;
            if (shouldRegisterSessionKeysOnSignin()) {
                const keypair = yield generateSessionKey();
                sessionPublicKey = keypair.publicKey;
            }
            yield generateSessionKeys();
            const apiCall = () => farcasterSignIn(environmentId, {
                address,
                captchaToken,
                domain,
                message,
                nonce,
                sessionPublicKey,
                signature,
            });
            yield signInAccount(ProviderEnum.Farcaster, apiCall);
        }
        else {
            const apiCall = () => farcasterVerify({
                environmentId,
                farcasterSignInRequest: {
                    address,
                    captchaToken,
                    domain,
                    message,
                    nonce,
                    signature,
                },
            });
            yield linkAccount(ProviderEnum.Farcaster, apiCall);
            setShowAuthFlow(false, { clearErrors: false });
        }
        setIsProcessing(false);
    }), [
        defaultRedirectUrl,
        environmentId,
        generateSessionKey,
        handleError,
        linkAccount,
        onFarcasterUrl,
        setShowAuthFlow,
        shouldRegisterSessionKeysOnSignin,
        signInAccount,
    ]);
    const connectSocialAccount = useCallback((_e) => __awaiter(void 0, [_e], void 0, function* ({ authMode, provider, validator, captchaToken, isHeadlessSocialSignIn, payingWithDynamic, triggerFundFromExchangeOnSuccess, redirectUrl, telegramAuthToken, showWidgetAfterConnection, forcePopup, ssoProviderId, requestedScopes, }) {
        var _f, _g, _h, _j, _k;
        clearError();
        setIsProcessing(true);
        if (!authMode) {
            logger.error('Auth mode is required. Please use either "link", "signin", or "stepup"');
            return;
        }
        if (!checkValidProvider(provider, authMode)) {
            return;
        }
        // invalid connect request
        if (validator && !validator(provider)) {
            return;
        }
        dynamicEvents.emit('authInit', {
            option: provider,
            provider,
            type: 'social',
        });
        if (provider === ProviderEnum.Farcaster) {
            return connectWithFarcaster(authMode, {
                captchaToken,
                redirectUrl,
            });
        }
        const state = randomString(32);
        const verifier = randomString(43);
        // Whether we will use PKCE (code verifier/challenge) for this provider
        const usingPkce = providersRequiringPkce.includes(provider);
        setSocialStorageFor(provider, {
            captchaToken,
            codeVerifier: usingPkce ? verifier : undefined,
            dynamicOauthSsoProviderId: ssoProviderId,
            isHeadlessSocialSignIn,
            mode: authMode,
            payingWithDynamic,
            requestedScopes,
            showWidgetAfterConnection,
            state,
            triggerFundFromExchangeOnSuccess,
        });
        const isGoogleDriveBackupEnabled = (_h = (_g = (_f = projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.sdk) === null || _f === void 0 ? void 0 : _f.waas) === null || _g === void 0 ? void 0 : _g.backupOptions) === null || _h === void 0 ? void 0 : _h.includes(WaasBackupOptionsEnum.GoogleDrive);
        const loginUrlString = getOauthLoginUrl((_j = projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.providers) !== null && _j !== void 0 ? _j : [], provider, { isGoogleDriveBackupEnabled });
        if (!loginUrlString) {
            handleError(provider, SocialOAuthErrorCode.NO_OAUTH_URL, `Failed to get login url to connect social account: ${provider}`, { raiseAuthFailure: authMode === 'signin' });
            return;
        }
        const oauthLoginUrl = new URL(loginUrlString);
        oauthLoginUrl.searchParams.set('state', state);
        // only add pkce params for providers that support/require it
        if (usingPkce) {
            const digest = yield digestSHA256(verifier);
            oauthLoginUrl.searchParams.set('code_challenge', encodeBase64URL(digest));
            oauthLoginUrl.searchParams.set('code_challenge_method', 'S256');
        }
        const isMobile$1 = isMobile();
        try {
            // Step-up normally forces a popup, but on mobile browsers popups
            // opened after an async gap (e.g. captcha verification) lose user
            // activation and are blocked (notably iOS Safari). Fall back to the
            // configured strategy (redirect) on mobile so the flow can resume.
            const effectiveStrategy = (authMode === 'stepup' && !isMobile$1) || forcePopup
                ? 'popup'
                : strategy;
            const authCode = yield Oauth2Service.getOauthCode({
                apiProvider: getProviderByType((_k = projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.providers) !== null && _k !== void 0 ? _k : [], provider),
                getOAuthResultFromApi: () => getOAuthResult(environmentId, provider, {
                    state,
                }),
                initWebAuth: ({ redirectUrl } = {}) => initAuth(environmentId, provider, {
                    redirectUrl: redirectUrl
                        ? removeDynamicOauthParamsFromUrl(redirectUrl)
                        : undefined,
                    state,
                }),
                isMobile: isMobile$1,
                oauthLoginUrl,
                onSettled,
                provider,
                redirectUrl: redirectUrl !== null && redirectUrl !== void 0 ? redirectUrl : defaultRedirectUrl,
                setIsProcessing,
                state,
                strategy: effectiveStrategy,
            });
            yield completeConnection({
                authCode,
                authMode,
                captchaToken,
                provider,
                requestedScopes,
                ssoProviderId,
                state,
                telegramAuthToken,
                verifier,
            });
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
        }
        catch (error) {
            if (Oauth2Service.isGetOauthCodeError(error)) {
                handleError(provider, error.code, error.message, {
                    raiseAuthFailure: authMode === 'signin',
                });
                throw error;
            }
            // Log message individually so the Expo console can display it (it fails to display objects)
            logger.error('Social connection failed with unexpected error:', error.message, error);
            onFailed(provider, { error }, { raiseAuthFailure: authMode === 'signin' });
        }
    }), [
        checkValidProvider,
        clearError,
        completeConnection,
        connectWithFarcaster,
        defaultRedirectUrl,
        environmentId,
        handleError,
        onFailed,
        onSettled,
        projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.providers,
        (_b = (_a = projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.sdk) === null || _a === void 0 ? void 0 : _a.waas) === null || _b === void 0 ? void 0 : _b.backupOptions,
        strategy,
    ]);
    const checkCaptchaAndConnect = useCallback((props) => __awaiter(void 0, void 0, void 0, function* () {
        if (props.authMode === 'signin' && getDynamicClient().user) {
            throw new DynamicError('User is already logged in', 'user_is_already_logged_in');
        }
        else if ((props.authMode === 'link' || props.authMode === 'stepup') &&
            !getDynamicClient().user) {
            throw new DynamicError('User is not logged in', 'user_is_not_logged_in');
        }
        if (!isCaptchaEnabled(projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.security)) {
            yield connectSocialAccount(props);
            return;
        }
        // Make sure the auth flow is visible
        // it would still be hidden in headless scenarios
        setShowAuthFlow(true, {
            initializeWalletConnect: false,
            performMultiWalletChecks: false,
        });
        engageCaptcha({
            authMethod: 'social',
            // Backing out of the captcha view abandons onCaptchaSuccess, so the
            // social flow must settle here — otherwise the social-redirect-view
            // beneath it spins forever waiting for a connection that never starts
            onCaptchaCancel: () => {
                onFailed(props.provider, {
                    error: {
                        code: SocialOAuthErrorCode.USER_CANCELLED,
                        message: 'User cancelled connection',
                    },
                }, { raiseAuthFailure: true });
            },
            onCaptchaSuccess: (captchaToken) => __awaiter(void 0, void 0, void 0, function* () {
                yield connectSocialAccount(Object.assign(Object.assign({}, props), { captchaToken }));
                setShowAuthFlow(false);
            }),
        });
    }), [
        setShowAuthFlow,
        connectSocialAccount,
        engageCaptcha,
        onFailed,
        projectSettings === null || projectSettings === void 0 ? void 0 : projectSettings.security,
    ]);
    return useMemo(() => ({
        checkValidProvider,
        completeConnection,
        connectSocialAccount: checkCaptchaAndConnect,
        error,
        handleError,
        isProcessing,
        setError,
        setIsProcessing,
    }), [
        checkValidProvider,
        completeConnection,
        checkCaptchaAndConnect,
        error,
        handleError,
        isProcessing,
    ]);
};
const removeDynamicOauthParamsFromUrl = (url) => {
    const urlObject = new URL(url);
    urlObject.searchParams.delete('dynamicOauthState');
    urlObject.searchParams.delete('dynamicOauthCode');
    return urlObject.toString();
};

export { useSocialAuth };
