import '../css/app.css';

import { createInertiaApp, router } from '@inertiajs/react';
import * as Sentry from '@sentry/react';
import axios from 'axios';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import {
    CircleCheckIcon,
    InfoIcon,
    Loader2Icon,
    OctagonXIcon,
    TriangleAlertIcon,
} from 'lucide-react';
import { StrictMode, useEffect, useState } from 'react';
import { createRoot } from 'react-dom/client';
import { toast, Toaster } from 'sonner';
import { EncryptionKeyProvider } from './contexts/encryption-key-context';
import { PrivacyModeProvider } from './contexts/privacy-mode-context';
import { SyncProvider } from './contexts/sync-context';
import { initializeTheme } from './hooks/use-appearance';
import { initializeChartColorScheme } from './hooks/use-chart-color-scheme';
import { installChunkLoadRecovery } from './lib/chunk-load-recovery';
import { initializePostHog } from './lib/posthog';
import {
    isBrowserExtensionNoise,
    isChunkLoadErrorEvent,
    isFacebookInAppBrowserJavaBridgeNoise,
    isPostMessageDataCloneNoise,
    isSafariCashbackExtensionNoise,
} from './lib/sentry';
import { showSubscriptionPaymentIssueToast } from './lib/subscription-payment-issue-toast';
import type { ExpiredBankingConnectionNotification, SharedData } from './types';
import { __, setTranslations } from './utils/i18n';

installChunkLoadRecovery();

// The installed PWA claims the whole origin (manifest scope "/"), so on mobile an
// OAuth link (e.g. from ChatGPT connecting over MCP) gets captured into the app.
// Paired with launch_handler "focus-existing", the launcher hands us the target
// URL instead of dropping it and showing the dashboard — navigate there so the
// OAuth consent screen actually loads. Non-OAuth launches keep their place.
type LaunchParams = { targetURL?: string };

const launchQueue = (
    window as Window & {
        launchQueue?: {
            setConsumer(consumer: (params: LaunchParams) => void): void;
        };
    }
).launchQueue;

if (launchQueue) {
    launchQueue.setConsumer(({ targetURL }) => {
        if (!targetURL) {
            return;
        }

        const target = new URL(targetURL);

        if (
            target.pathname.startsWith('/oauth/') &&
            target.href !== window.location.href
        ) {
            window.location.href = targetURL;
        }
    });
}

Sentry.init({
    dsn: import.meta.env.SENTRY_LARAVEL_DSN,
    environment: import.meta.env.MODE,
    integrations: [],
    tracesSampleRate: 0,
    sendDefaultPii: true,
    beforeSend(event) {
        if (
            isChunkLoadErrorEvent(event) ||
            isBrowserExtensionNoise(event) ||
            isPostMessageDataCloneNoise(event) ||
            isFacebookInAppBrowserJavaBridgeNoise(event) ||
            isSafariCashbackExtensionNoise(event)
        ) {
            return null;
        }

        return event;
    },
    enabled:
        import.meta.env.MODE === 'production' &&
        Boolean(import.meta.env.SENTRY_LARAVEL_DSN),
});

initializePostHog();

// Initialize theme before creating the app so progress bar color is correct
initializeTheme();
initializeChartColorScheme();

const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
let hasAttemptedTimezoneBackfill = false;
const notifiedExpiredConnectionIds = new Set<string>();

function showExpiredConnectionsToast(
    expiredConnections: ExpiredBankingConnectionNotification[] | undefined,
): void {
    if (!expiredConnections || expiredConnections.length === 0) {
        return;
    }

    const newExpiredConnections = expiredConnections.filter(
        (connection) => !notifiedExpiredConnectionIds.has(connection.id),
    );

    if (newExpiredConnections.length === 0) {
        return;
    }

    expiredConnections.forEach((connection) => {
        notifiedExpiredConnectionIds.add(connection.id);
    });

    const firstConnection = expiredConnections[0];
    const count = expiredConnections.length;

    toast.error(
        count === 1
            ? __('Your :provider connection has expired.', {
                  provider: firstConnection.aspsp_name,
              })
            : __('You have :count expired bank connections.', {
                  count,
              }),
        {
            description: __('Reconnect to resume automatic syncing.'),
            duration: Infinity,
            action: {
                label: __('Reconnect'),
                onClick: () => {
                    window.location.href = firstConnection.reconnect_url;
                },
            },
        },
    );
}

function ExpiredConnectionsToast({
    initialExpiredConnections,
}: {
    initialExpiredConnections: ExpiredBankingConnectionNotification[];
}) {
    useEffect(() => {
        showExpiredConnectionsToast(initialExpiredConnections);

        return router.on('navigate', (event) => {
            const pageProps = event.detail.page.props as unknown as SharedData;
            showExpiredConnectionsToast(pageProps.expiredBankingConnections);
        });
    }, [initialExpiredConnections]);

    return null;
}

// Determine progress bar color based on current theme
const getProgressBarColor = () => {
    const isDark = document.documentElement.classList.contains('dark');
    return isDark ? '#EEE' : '#4B5563'; // gray-400 for dark mode, gray-600 for light mode
};

const isOnboardingPath = () =>
    typeof window !== 'undefined' &&
    window.location.pathname.startsWith('/onboarding');

// Onboarding has no bottom navigation bar, so toasts sit flush at the bottom
// center instead of being lifted to clear the (absent) mobile tab bar.
function AppToaster() {
    const [isOnboarding, setIsOnboarding] = useState(isOnboardingPath);

    useEffect(() => {
        return router.on('navigate', () => setIsOnboarding(isOnboardingPath()));
    }, []);

    return (
        <Toaster
            richColors
            position={isOnboarding ? 'bottom-center' : undefined}
            mobileOffset={{ bottom: isOnboarding ? '16px' : '110px' }}
            icons={{
                success: <CircleCheckIcon className="size-4" />,
                info: <InfoIcon className="size-4" />,
                warning: <TriangleAlertIcon className="size-4" />,
                error: <OctagonXIcon className="size-4" />,
                loading: <Loader2Icon className="size-4 animate-spin" />,
            }}
        />
    );
}

createInertiaApp({
    title: (title) => (title ? `${title} - ${appName}` : appName),
    resolve: (name) =>
        resolvePageComponent(
            `./pages/${name}.tsx`,
            import.meta.glob('./pages/**/*.tsx'),
        ),
    setup({ el, App, props }) {
        const root = createRoot(el);
        const initialPageProps = props.initialPage?.props as
            | Partial<SharedData>
            | undefined;
        const initialUser = initialPageProps?.auth?.user ?? null;
        const initialIsAuthenticated = Boolean(initialUser);
        const hasEncryptionSetup =
            (initialPageProps?.hasEncryptionSetup as boolean) ?? false;
        const hasEncryptedAccounts =
            (initialPageProps?.hasEncryptedAccounts as boolean) ?? false;
        const hasEncryptedTransactions =
            (initialPageProps?.hasEncryptedTransactions as boolean) ?? false;
        const initialExpiredConnections =
            (initialPageProps?.expiredBankingConnections as
                | ExpiredBankingConnectionNotification[]
                | undefined) ?? [];
        const initialSubscriptionPaymentIssue =
            initialPageProps?.subscriptionPaymentIssue;

        const syncUserTimezone = async (pageProps?: Partial<SharedData>) => {
            const user = pageProps?.auth?.user ?? null;
            const detectedTimezone =
                Intl.DateTimeFormat().resolvedOptions().timeZone;

            if (
                hasAttemptedTimezoneBackfill ||
                !user ||
                user.timezone ||
                !detectedTimezone
            ) {
                return;
            }

            hasAttemptedTimezoneBackfill = true;

            try {
                await axios.patch('/settings/timezone', {
                    timezone: detectedTimezone,
                });
            } catch {
                hasAttemptedTimezoneBackfill = false;
            }
        };

        // Initialize translations from server-rendered page data
        setTranslations(
            (initialPageProps?.translations as Record<string, string>) ?? {},
        );

        // Keep translations in sync on every Inertia navigation
        router.on('navigate', (event) => {
            const pageProps = event.detail.page.props as unknown as SharedData;
            setTranslations(
                (pageProps?.translations as Record<string, string>) ?? {},
            );

            showSubscriptionPaymentIssueToast(
                pageProps.subscriptionPaymentIssue,
            );

            void syncUserTimezone(pageProps);
        });

        showSubscriptionPaymentIssueToast(initialSubscriptionPaymentIssue);

        void syncUserTimezone(initialPageProps);

        root.render(
            <StrictMode>
                <EncryptionKeyProvider
                    hasEncryptionSetup={
                        hasEncryptionSetup &&
                        (hasEncryptedAccounts || hasEncryptedTransactions)
                    }
                >
                    <PrivacyModeProvider>
                        <SyncProvider
                            initialIsAuthenticated={initialIsAuthenticated}
                            initialUser={initialUser}
                        >
                            <App {...props} />
                            <ExpiredConnectionsToast
                                initialExpiredConnections={
                                    initialExpiredConnections
                                }
                            />
                            <AppToaster />
                        </SyncProvider>
                    </PrivacyModeProvider>
                </EncryptionKeyProvider>
            </StrictMode>,
        );
    },
    progress: {
        color: getProgressBarColor(),
    },
});
