'use client';

import { ChakraProvider, extendTheme } from '@chakra-ui/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { RouterProvider } from 'react-aria-components';
import { CookiesProvider } from 'react-cookie';
import { I18nextProvider } from 'react-i18next';
import { useIsClient } from 'usehooks-ts';

import { UploadStateProvider } from '@/app/(root)/create/uploaderV2/UploadStateContext';
import CreateFormContextProvider from '@/app/(root)/create/v2/CreateFormContextProvider';
import { DragAndDropProvider } from '@/app/(root)/dragAndDrop/DragAndDropContext';
import { HookFormProvider } from '@/app/(root)/hooks/create/useHookForm';
import { LiveRadioProvider } from '@/app/(root)/live-radio/LiveRadioProvider';
import { GenerationNotificationProvider } from '@/app/(root)/videogen/GenerationNotificationProvider';
import { ClipBrowserRegistryContextProvider } from '@/components/clipBrowser/useClipBrowserRegistry';
import { DialogContextProvider } from '@/components/modal/DialogModal';
import { StudioLoadingOverlayProvider } from '@/components/studio/StudioLoadingOverlay';
import {
  AnalyticsProvider,
  StatsigUserContextProps,
} from '@/context/AnalyticsContext';
import { ExperimentsProvider } from '@/context/ExperimentsContext';
import { HooksFeedProvider } from '@/context/HooksFeedContext';
import { MobileBannerProvider } from '@/context/MobileBannerContext';
import { ModalProvider } from '@/context/ModalContext';
import { PreviewProvider } from '@/context/PreviewContext';
import { RootThemeProvider } from '@/context/ThemeContext';
import composeProviders from '@/context/composeProviders';
import { ContestClipProvider } from '@/hooks/useContestClip';
import { getInstance as getApiClientInstance } from '@/lib/apiClient';
import cookies from '@/lib/cookies';
import makeRootStore, { RootStore } from '@/state/rootStore';
import { SUPER_MODAL_Z_INDEX } from '@/utils/constants';
import { eventLogger } from '@/utils/event-logger';

import i18n from '../i18n';

// Ridiculous fix for https://github.com/facebook/react/issues/11538#issuecomment-417504600
if (typeof Node === 'function' && Node.prototype) {
  const originalRemoveChild = Node.prototype.removeChild;
  Node.prototype.removeChild = function <T extends Node>(child: T): T {
    if (child.parentNode !== this) {
      if (console) {
        console.error(
          'Cannot remove a child from a different parent',
          child,
          this
        );
      }
      return child;
    }
    return originalRemoveChild.apply(this, arguments as any) as any;
  };

  const originalInsertBefore = Node.prototype.insertBefore;
  Node.prototype.insertBefore = function <T extends Node>(
    newNode: T,
    referenceNode: Node
  ): T {
    if (referenceNode && referenceNode.parentNode !== this) {
      if (console) {
        console.error(
          'Cannot insert before a reference node from a different parent',
          referenceNode,
          this
        );
      }
      return newNode;
    }
    return originalInsertBefore.apply(this, arguments as any) as any;
  };
}

/**
 * Creates a TanStack Query client instance
 *
 * We use a factory function instead of returning a singleton instance directly
 * because we don't want to share the query cache between all SSR requests
 *
 * https://tanstack.com/query/latest/docs/framework/react/guides/ssr#initial-setup
 */
export function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        retry: false,
        refetchOnWindowFocus: false,
        refetchOnMount: false,
      },
    },
  });
}

/**
 * MobX root store
 */
export const RootStoreContext = createContext<RootStore | undefined>(undefined);

export type RootAppProviderProps = {
  geolocation?: Pick<
    StatsigUserContextProps,
    | 'country'
    | 'postalCode'
    | 'latitude'
    | 'longitude'
    | 'city'
    | 'region'
    | 'forwardedFor'
  >;
};

/**
 * The Root App Provider creates the API client, query client, and root store
 * and makes them avialable for the rest of the app
 *
 * It also bootstraps the analytics context and sets the router used by React
 * Aria components.
 *
 * Finally, it kicks off the initial session load.
 */
export const RootAppProvider: React.FC<
  React.PropsWithChildren<RootAppProviderProps>
> = (props) => {
  const { geolocation, children } = props;

  const [apiClient] = useState(() => getApiClientInstance());
  const [queryClient] = useState(() => makeQueryClient());
  const rootStore = useMemo(
    () => makeRootStore(apiClient, queryClient, eventLogger),
    [apiClient, queryClient]
  );

  const router = useRouter();

  const isClient = useIsClient();
  useEffect(() => {
    if (isClient && !rootStore.session.sessionIsLoading) {
      rootStore.session.loadSession({ retries: 1, forceLoad: false });
    }
  }, [isClient, rootStore]);

  return (
    <RootStoreContext.Provider value={rootStore}>
      <QueryClientProvider client={queryClient}>
        <AnalyticsProvider {...geolocation}>
          <RouterProvider navigate={router.push}>{children}</RouterProvider>
        </AnalyticsProvider>
      </QueryClientProvider>
    </RootStoreContext.Provider>
  );
};

/**
 * Access MobX stores for use in components
 *
 * Note that you must wrap any components accessing the stores in
 * `observer(...)` otherwise they will not get reactive updates
 */
export function useStores() {
  const rootStore = useContext(RootStoreContext);
  if (!rootStore) {
    throw new Error('Root store not found');
  }
  return rootStore;
}

/**
 * Chakra UI theme
 */
export const chakraTheme = extendTheme({
  config: {
    initialColorMode: 'dark',
    useSystemColorMode: false,
  },
  fonts: {
    heading:
      "'Neue Montreal', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI'",
    body: "'Neue Montreal', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI'",
  },
  components: {
    Menu: {
      baseStyle: {
        list: {
          borderColor: 'var(--color-border-primary)',
        },
      },
    },
    Tooltip: {
      baseStyle: {
        // pass the z-index to the tooltip because in the case where it is nested in the song menu, it gets covered
        zIndex: SUPER_MODAL_Z_INDEX + 1,
      },
    },
  },
});

/**
 * Core Providers set up the basic context that we need pretty much everywhere
 *
 * @param props.children - The children to render
 * @param props.app.geolocation - The statsig client
 */
export const CoreProviders = composeProviders<{ app?: RootAppProviderProps }>(
  // Cookies
  [CookiesProvider, { cookies }],
  // Localization
  [I18nextProvider, { i18n }],
  // Root MobX store, query client, API client, and analytics
  [RootAppProvider, 'app'],
  // Suno Theme
  RootThemeProvider,
  // Chakra UI theme
  [
    ChakraProvider,
    { theme: chakraTheme, disableGlobalStyle: true, resetCSS: false },
  ]
);

/**
 * Web App Providers set up the additional contexts that are relevant to the
 * Suno app, but not necessarily standalone areas like the landing page and
 * embeds.
 */
export const WebAppProviders = composeProviders(
  // Suno Dialog modals
  DialogContextProvider,
  // Drag and Drop
  DragAndDropProvider,
  // Mobile Banner
  MobileBannerProvider,
  // Hooks Feed
  HooksFeedProvider,
  // Hooks Creation Form
  HookFormProvider,
  // Contest Clip
  ContestClipProvider,
  // Clip Browser Registry
  ClipBrowserRegistryContextProvider,
  // Live Radio
  LiveRadioProvider,
  // Upload State
  UploadStateProvider,
  // Modal
  ModalProvider,
  // Generation Notifications (depends on ModalProvider)
  GenerationNotificationProvider,
  // Clip Preview
  PreviewProvider,
  // Create Form state
  CreateFormContextProvider,
  // Studio Loading State
  StudioLoadingOverlayProvider,
  // Global statsig experiments
  ExperimentsProvider
);

/**
 * Standard app providers
 *
 * This is the drop-in equivalent of `chakraProviders` + `ClientLayout`
 */
export const AppProviders: React.FC<
  React.PropsWithChildren<{ app?: RootAppProviderProps }>
> = ({ children, app }) => (
  <CoreProviders app={app}>
    <WebAppProviders>{children}</WebAppProviders>
  </CoreProviders>
);
