import * as React from 'react';
import UsercentricsClient, { UI_LAYER } from '@usercentrics/cmp-browser-sdk';
import type { InitialUIValues, Category, UI, Maybe, DefaultLabels } from '@usercentrics/cmp-browser-sdk';

import { DecisionsProvider } from '../DecisionsContext';
import type { ContextValue, State, UsercentricsHandlers, UsercentricsProviderProps } from './types';

import { loadUsercentrics } from './loadUsercentrics';

export const defaultConsentEventName = 'dataLayerUC';

export const UsercentricsContext = React.createContext({
  status: 'initializing',
} as ContextValue);

const initialState: State = {
  status: 'initializing',
  settings: null,
  settingsLabels: null,
  categories: [],
  activeLayer: UI_LAYER.NONE,
};

type Action =
  | {
      type: 'INITIALIZED';
      payload: {
        categories: Category[];
        activeLayer: UI_LAYER;
        settings: Maybe<UI>;
        settingsLabels: DefaultLabels;
      };
    }
  | { type: 'UPDATED_CATEGORIES'; payload: { categories: Category[] } }
  | { type: 'ERROR'; status: 'initializing_failed' | 'updating_failed' }
  | { type: 'UPDATING' }
  | { type: 'SET_ACTIVE_LAYER'; layer: UI_LAYER };

const reducer: React.Reducer<State, Action> = (state, action) => {
  switch (action.type) {
    case 'UPDATING':
      return { ...state, status: 'updating' };
    case 'ERROR':
      return { ...state, status: action.status };
    case 'INITIALIZED':
      return { ...state, ...action.payload, status: 'initialized' };
    case 'UPDATED_CATEGORIES':
      return { ...state, ...action.payload, status: 'updated' };
    case 'SET_ACTIVE_LAYER':
      return { ...state, activeLayer: action.layer };
    default:
      return state;
  }
};

const Provider = ({
  children,
  settingsId,
  options,
  abTestValue,
  consentEventName = defaultConsentEventName,
  activeLayer,
  loadingUI: LoadingUI = () => <></>,
}: React.PropsWithChildren<UsercentricsProviderProps>) => {
  const clientRef = React.useRef<UsercentricsClient | null>(null);
  const [state, dispatch] = React.useReducer(reducer, initialState);

  React.useEffect(() => {
    if (settingsId) {
      const Client = loadUsercentrics();
      const client: UsercentricsClient = new Client(settingsId, options);

      client
        .init()
        .then((initialUIValues: InitialUIValues) => {
          clientRef.current = client;
          const settings = client.getSettingsUI();
          const settingsLabels = client.getSettingsLabels() as DefaultLabels;

          client.getCategoriesFullInfo().then((categories) => {
            dispatch({
              type: 'INITIALIZED',
              payload: {
                settings,
                settingsLabels,
                categories,
                activeLayer: activeLayer ?? initialUIValues.initialLayer,
              },
            });
          });
        })
        .catch((error: unknown) => {
          dispatch({ type: 'ERROR', status: 'initializing_failed' });
          console.error(error);
        });
    }
  }, [options, settingsId, activeLayer]);

  const handlers = React.useMemo<UsercentricsHandlers | null>(() => {
    if (state.status === 'initializing') {
      return null;
    }

    if (!clientRef.current) {
      return null;
    }

    const client = clientRef.current;

    const updateCategories = async () => {
      try {
        const categories = await client.getCategoriesFullInfo();
        dispatch({
          type: 'UPDATED_CATEGORIES',
          payload: { categories },
        });
      } catch (error) {
        console.error(error);
      }
    };

    return {
      accept: async (userDecisions) => {
        try {
          dispatch({ type: 'UPDATING' });
          await client.updateServices(userDecisions);
          updateCategories();
        } catch (error) {
          dispatch({ type: 'ERROR', status: 'updating_failed' });
          console.error(error);
        }
      },
      acceptAll: async () => {
        try {
          dispatch({ type: 'UPDATING' });
          await client.acceptAllServices();
          updateCategories();
          dispatch({ type: 'SET_ACTIVE_LAYER', layer: UI_LAYER.NONE });
        } catch (error) {
          dispatch({ type: 'ERROR', status: 'updating_failed' });
          console.error(error);
        }
      },
      denyAll: async () => {
        try {
          dispatch({ type: 'UPDATING' });
          await client.denyAllServices();
          updateCategories();
        } catch (error) {
          dispatch({ type: 'ERROR', status: 'updating_failed' });
          console.error(error);
        }
      },
      setActiveLayer: (layer: UI_LAYER) =>
        dispatch({
          type: 'SET_ACTIVE_LAYER',
          layer,
        }),
    };
  }, [state.status]);

  if (state.status === 'initializing') {
    return <LoadingUI />;
  }

  return (
    <UsercentricsContext.Provider
      value={{
        handlers,
        appearance: options?.appearance ?? 'wall',
        locale: options?.language ?? 'en',
        showCloseAndAcceptAllButton: options?.showCloseAndAcceptAllButton ?? false,
        abTestValue,
        consentEventName,
        ...state,
      }}
    >
      <DecisionsProvider categories={state.categories} status={state.status}>
        {children}
      </DecisionsProvider>
    </UsercentricsContext.Provider>
  );
};

type UseUsercentrics = () => ContextValue;

export const useUsercentrics: UseUsercentrics = () => {
  const context = React.useContext(UsercentricsContext);
  return context;
};

export const UsercentricsProvider = ({
  disabled,
  ...rest
}: React.PropsWithChildren<UsercentricsProviderProps>): JSX.Element => {
  if (disabled) {
    return <>{rest.children}</>;
  }

  return <Provider {...rest} />;
};
