import { Locale as DateFnsLocale } from 'date-fns';
import {
  AnchorHTMLAttributes,
  ForwardRefExoticComponent,
  ReactNode,
  RefAttributes,
  useMemo,
  useState,
} from 'react';
import {
  StyleSheetManager,
  DefaultTheme as StyledComponentsDefaultTheme,
  StylisPlugin,
  ThemeProvider,
} from 'styled-components/macro';

import { DefaultTheme } from '../theme/types';
import { LocaleIdentifier } from '../types';
import { assertEmptyObject } from '../utils/assertEmptyObject';

import { HiveUIContextProvider } from './contexts/HiveUIContext';
import { LocaleContextProvider } from './contexts/LocaleContext/LocaleContext';
import { TestIdAttributeContextProvider } from './contexts/TestIdAttributeContext/TestIdAttributeContext';
import { TimeZoneContextProvider } from './contexts/TimeZoneContext/TimeZoneContext';
import { TranslationContextProvider } from './contexts/TranslationContext/TranslationContext';
import { I18n } from './contexts/TranslationContext/types';
import { ToastManager } from './managers/ToastManager/ToastManager';
import { GlobalStyle } from './styled';

/**
 * Props for {@link LinkComponent}
 */
export interface LinkComponentProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
  to: string | Record<string, unknown>;
}

/**
 * Component will be used as a link,
 * can be the react-router Link component or a custom with an appropriate interface
 */
export type LinkComponent = ForwardRefExoticComponent<LinkComponentProps & RefAttributes<HTMLAnchorElement>>;

export type Language = string;

/** Hive UI Kit language configuration */
export interface LanguageConfig {
  /** Two char language code */
  language: Language;
  /** Locale that should be used as default if user local is not found */
  defaultLocale: LocaleIdentifier;
  /** Locales for selected language */
  locales: {
    [locale: LocaleIdentifier]: {
      dateFns: DateFnsLocale;
    };
  };
}

export interface HiveUIProps {
  /**
   * Configuration of supported languages.
   *
   * ```ts
   * import en_AU from 'date-fns/locale/en-AU';
   * import en_CA from 'date-fns/locale/en-CA';
   * import en_NZ from 'date-fns/locale/en-NZ';
   * import en_US from 'date-fns/locale/en-US';
   * import ru_RU from 'date-fns/locale/ru';
   *
   * const languages = [
   *   {
   *     language: 'en',
   *     defaultLocale: Locale.English_USA,
   *     locales: {
   *       [Locale.English_USA]: {
   *         dateFns: en_US,
   *       },
   *       [Locale.English_Canada]: {
   *         dateFns: en_CA,
   *       },
   *       [Locale.English_Australia]: {
   *         dateFns: en_AU,
   *       },
   *       [Locale.English_NewZealand]: {
   *         dateFns: en_NZ,
   *       },
   *     },
   *   },
   *   {
   *     language: 'ru',
   *     defaultLocale: Locale.Russian_Russia,
   *     locales: {
   *       [Locale.Russian_Russia]: {
   *         dateFns: ru_RU,
   *       },
   *     },
   *   },
   * ];
   * ```
   */
  languages: LanguageConfig[];
  /**
   * Instance of {@link i18n}.
   *
   * ```ts
   * import { createInstance } from 'i18next';
   *
   * const i18n = createInstance();
   *
   * // Don't forget to init i18n instance
   * i18n.init({
   *   resources: {
   *     en: {
   *       translation: en
   *     },
   *     ru: {
   *       translation: ru
   *     },
   *   },
   *   lng: 'en', // if you're using a language detector, do not define the lng option
   *   fallbackLng: 'en',
   *
   *   interpolation: {
   *     escapeValue: false, // react already safes from XSS
   *   },
   * });
   * ```
   */
  i18n: I18n;
  /** Theme that should be used in UI Kit */
  theme: DefaultTheme;
  /** Time zone should be used in UI Kit */
  timeZone?: string;
  children: ReactNode;
  preferredLocale?: LocaleIdentifier;
  shadowRoot?: ShadowRoot;
  /**
   * Component that will be used as a link,
   * can be the react-router Link component or a custom with an appropriate interface
   */
  linkComponent: LinkComponent;
  /**
   * Attribute that will be used for storing value of `testId` props in components.
   *
   * @default `data-test`.
   */
  testIdAttribute?: string;
}

// It's just sugar to legitimize the syntax to the linter.
const stylisMixinPlugin = ((context: number, content: string) => {
  const rule = '@mixin ';
  if (content.startsWith(rule)) {
    return content.slice(rule.length);
  }
}) as unknown as StylisPlugin; // StylisPlugin type is incorrect

Object.defineProperty(stylisMixinPlugin, 'name', { value: 'stylisMixinPlugin' });

/** Base provider that pass main context data inside Hive UI kit components. */
export function HiveUI(props: HiveUIProps) {
  const {
    timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone,
    languages,
    preferredLocale = navigator.language,
    linkComponent,
    testIdAttribute = 'data-test',
    theme,
    children,
    i18n,
    shadowRoot,
    ...rest
  } = props;
  assertEmptyObject(rest);

  const [rootElement, setRootElement] = useState<HTMLElement | null>(null);

  const currentLanguage = i18n.language;

  const locale = useMemo(() => {
    // ensure that every language has configuration for defaultLocale
    for (const item of languages) {
      const defaultLocale = item.defaultLocale;
      if (!(defaultLocale in item.locales)) {
        throw new Error(
          `Cannot find configuration for "${defaultLocale}" locale in "${item.language}" language`,
        );
      }
    }

    const language = languages.find((lang) => lang.language === currentLanguage);
    if (!language) {
      throw new Error(`Cannot find language configuration for "${currentLanguage}" language code`);
    }

    return language.locales[preferredLocale] ? preferredLocale : language.defaultLocale;
  }, [preferredLocale, languages, currentLanguage]);

  return (
    <StyleSheetManager stylisPlugins={[stylisMixinPlugin]} target={shadowRoot as unknown as HTMLElement}>
      <TranslationContextProvider i18n={i18n}>
        <HiveUIContextProvider languages={languages} linkComponent={linkComponent} rootElement={rootElement}>
          <ThemeProvider theme={theme as StyledComponentsDefaultTheme}>
            <LocaleContextProvider locale={locale}>
              <TimeZoneContextProvider timeZone={timeZone}>
                <TestIdAttributeContextProvider testIdAttribute={testIdAttribute}>
                  <GlobalStyle ref={setRootElement}>
                    <ToastManager>{children}</ToastManager>
                  </GlobalStyle>
                </TestIdAttributeContextProvider>
              </TimeZoneContextProvider>
            </LocaleContextProvider>
          </ThemeProvider>
        </HiveUIContextProvider>
      </TranslationContextProvider>
    </StyleSheetManager>
  );
}
