import { OpenFeatureProvider } from '@openfeature/react-sdk';
import { UNSAFE_PortalProvider } from '@react-aria/overlays';
import { type Action, KBarProvider } from 'kbar';
import { Component, type ComponentType, Fragment, type ReactNode } from 'react';
import CacheProvider from 'react-inlinesvg/provider';
import { Provider } from 'react-redux';
import { Route, Routes } from 'react-router-dom-v5-compat';

import { config, navigationLogger, reportInteraction } from '@grafana/runtime';
import { getFeatureFlagClient } from '@grafana/runtime/internal';
import { ErrorBoundaryAlert, getPortalContainer, GlobalStyles, PortalContainer, TimeRangeProvider } from '@grafana/ui';
import { getAppRoutes } from 'app/routes/routes';
import { store } from 'app/store/store';

import { ExtensionSidebarContextProvider } from './core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider';
import { GrafanaContext, type GrafanaContextType } from './core/context/GrafanaContext';
import { GrafanaRouteWrapper } from './core/navigation/GrafanaRoute';
import { type RouteDescriptor } from './core/navigation/types';
import { ThemeProvider } from './core/utils/ConfigProvider';
import { LiveConnectionWarning } from './features/live/LiveConnectionWarning';
import { ExtensionRegistriesProvider } from './features/plugins/extensions/ExtensionRegistriesContext';
import { getPluginExtensionRegistries } from './features/plugins/extensions/registry/setup';
import { type PluginExtensionRegistries } from './features/plugins/extensions/registry/types';
import { ScopesContextProvider } from './features/scopes/ScopesContextProvider';
import { RouterWrapper } from './routes/RoutesWrapper';

interface AppWrapperProps {
  context: GrafanaContextType;
}

interface AppWrapperState {
  ready?: boolean;
  registries?: PluginExtensionRegistries;
}

/** Used by enterprise */
let bodyRenderHooks: ComponentType[] = [];
let pageBanners: ComponentType[] = [];
const enterpriseProviders: Array<ComponentType<{ children: ReactNode }>> = [];

export function addEnterpriseProviders(provider: ComponentType<{ children: ReactNode }>) {
  enterpriseProviders.push(provider);
}

export function addBodyRenderHook(fn: ComponentType) {
  bodyRenderHooks.push(fn);
}

export function addPageBanner(fn: ComponentType) {
  pageBanners.push(fn);
}

export class AppWrapper extends Component<AppWrapperProps, AppWrapperState> {
  private iconCacheID = `grafana-icon-cache-${config.buildInfo.commit}`;

  constructor(props: AppWrapperProps) {
    super(props);
    this.state = {};
  }

  async componentDidMount() {
    const registries = await getPluginExtensionRegistries();
    this.setState({ ready: true, registries });
    this.removePreloader();

    // clear any old icon caches
    const cacheKeys = (await window.caches?.keys()) ?? [];
    for (const key of cacheKeys) {
      if (key.startsWith('grafana-icon-cache') && key !== this.iconCacheID) {
        window.caches.delete(key);
      }
    }
  }

  removePreloader() {
    const preloader = document.querySelector('.preloader');
    if (preloader) {
      preloader.remove();
    } else {
      console.warn('Preloader element not found');
    }
  }

  renderRoute = (route: RouteDescriptor) => {
    return (
      <Route
        caseSensitive={route.sensitive === undefined ? false : route.sensitive}
        path={route.path}
        key={route.path}
        element={<GrafanaRouteWrapper route={route} />}
      />
    );
  };

  renderRoutes() {
    return <Routes>{getAppRoutes().map((r) => this.renderRoute(r))}</Routes>;
  }

  render() {
    const { context } = this.props;
    const { ready, registries } = this.state;

    navigationLogger('AppWrapper', false, 'rendering');

    const commandPaletteActionSelected = (action: Action) => {
      reportInteraction('command_palette_action_selected', {
        actionId: action.id,
        actionName: action.name,
      });
    };

    const routerWrapperProps = {
      routes: ready && this.renderRoutes(),
      pageBanners,
      bodyRenderHooks,
      providers: enterpriseProviders,
    };

    const MaybeTimeRangeProvider = config.featureToggles.timeRangeProvider ? TimeRangeProvider : Fragment;

    return (
      <Provider store={store}>
        <ErrorBoundaryAlert boundaryName="app-wrapper" style="page">
          <OpenFeatureProvider client={getFeatureFlagClient()}>
            <GrafanaContext.Provider value={context}>
              <ThemeProvider value={config.theme2}>
                <CacheProvider name={this.iconCacheID}>
                  <KBarProvider
                    actions={[]}
                    options={{ enableHistory: true, callbacks: { onSelectAction: commandPaletteActionSelected } }}
                  >
                    <MaybeTimeRangeProvider>
                      <ScopesContextProvider>
                        <ExtensionRegistriesProvider registries={registries}>
                          <ExtensionSidebarContextProvider>
                            <UNSAFE_PortalProvider getContainer={getPortalContainer}>
                              <GlobalStyles />
                              <div className="grafana-app">
                                <RouterWrapper {...routerWrapperProps} />
                                <LiveConnectionWarning />
                                <PortalContainer />
                              </div>
                            </UNSAFE_PortalProvider>
                          </ExtensionSidebarContextProvider>
                        </ExtensionRegistriesProvider>
                      </ScopesContextProvider>
                    </MaybeTimeRangeProvider>
                  </KBarProvider>
                </CacheProvider>
              </ThemeProvider>
            </GrafanaContext.Provider>
          </OpenFeatureProvider>
        </ErrorBoundaryAlert>
      </Provider>
    );
  }
}
