import { createInertiaApp } from '@inertiajs/react';
import {
    Component,
    lazy,
    Suspense,
    type ErrorInfo,
    type LazyExoticComponent,
    type ComponentType,
    type ReactNode,
} from 'react';
import { Toaster } from '@/components/ui/sonner';
import { TooltipProvider } from '@/components/ui/tooltip';
import { initializeTheme } from '@/hooks/use-appearance';

function normalizeError(reason: unknown): Error {
    if (reason instanceof Error) {
        return reason;
    }

    return new Error(String(reason));
}

class AppErrorBoundary extends Component<
    { children: ReactNode },
    { error: Error | null }
> {
    state = { error: null };

    static getDerivedStateFromError(error: Error): { error: Error } {
        return { error };
    }

    componentDidMount(): void {
        window.addEventListener('unhandledrejection', this.onUnhandledRejection);
    }

    componentWillUnmount(): void {
        window.removeEventListener(
            'unhandledrejection',
            this.onUnhandledRejection,
        );
    }

    componentDidCatch(error: Error, info: ErrorInfo): void {
        console.error('[AppError]', error, info);
    }

    onUnhandledRejection = (event: PromiseRejectionEvent): void => {
        event.preventDefault();
        this.setState({ error: normalizeError(event.reason) });
    };

    render(): ReactNode {
        if (this.state.error) {
            const error = this.state.error;

            return (
                <div
                    style={{
                        padding: 24,
                        fontFamily: 'monospace',
                        background: '#fee2e2',
                        color: '#991b1b',
                        whiteSpace: 'pre-wrap',
                        overflowY: 'auto',
                        height: '100vh',
                    }}
                >
                    <strong>{error.message}</strong>
                    {'\n\n'}
                    {error.stack}
                </div>
            );
        }

        return this.props.children;
    }
}

function lazyWithRetry<T extends ComponentType<unknown>>(
    factory: () => Promise<{ default: T }>,
): LazyExoticComponent<T> {
    return lazy(async () => {
        try {
            return await factory();
        } catch (error) {
            const message =
                error instanceof Error ? error.message : String(error);

            if (
                message.includes('Failed to fetch dynamically imported module') ||
                message.includes('Importing a module script failed') ||
                message.includes('error loading dynamically imported module')
            ) {
                window.location.reload();
            }

            throw error;
        }
    });
}

function PageLoadingFallback(): ReactNode {
    return (
        <div className="flex min-h-svh items-center justify-center bg-background text-sm text-muted-foreground">
            Loading…
        </div>
    );
}

const AppLayout = lazyWithRetry(() => import('@/layouts/app-layout'));
const AuthLayout = lazyWithRetry(() => import('@/layouts/auth-layout'));
const BookingWizardLayout = lazyWithRetry(
    () => import('@/layouts/booking-wizard-layout'),
);
const PublicLayout = lazyWithRetry(() => import('@/layouts/public-layout'));
const SettingsLayout = lazyWithRetry(() => import('@/layouts/settings/layout'));

const appName = import.meta.env.VITE_APP_NAME || 'Laravel';

try {
    initializeTheme();
} catch {
    // Theme setup must never prevent Inertia from booting.
}

createInertiaApp({
    title: (title) => (title ? `${title} - ${appName}` : appName),
    layout: (name) => {
        switch (true) {
            case name === 'welcome':
                return null;
            case name.startsWith('customer/'):
                return null;
            case name.startsWith('auth/'):
                return AuthLayout;
            case name.startsWith('settings/'):
                return [AppLayout, SettingsLayout];
            case name.startsWith('booking/wizard/'):
                return BookingWizardLayout;
            case name === 'booking/pay-balance':
                return PublicLayout;
            case name.startsWith('track/'):
                return PublicLayout;
            default:
                return AppLayout;
        }
    },
    strictMode: true,
    withApp(app) {
        return (
            <AppErrorBoundary>
                <Suspense fallback={<PageLoadingFallback />}>
                    <TooltipProvider delayDuration={0}>
                        {app}
                        <Toaster />
                    </TooltipProvider>
                </Suspense>
            </AppErrorBoundary>
        );
    },
    progress: {
        color: '#4B5563',
    },
});
