import createCache from '@emotion/cache';
import { ObjectInterpolation, serializeStyles } from '@emotion/serialize';
import { StyleSheet } from '@emotion/sheet';
import { EmotionCache, insertStyles } from '@emotion/utils';
import {
  Renderer,
  RendererRenderGlobal,
  RendererRenderFont,
  RendererRenderRule,
} from '@fluentui/react-northstar-styles-renderer';
// @ts-ignore No typings :(
import focusVisiblePlugin from '@quid/stylis-plugin-focus-visible';
// @ts-ignore No typings :(
import rtlPlugin from 'stylis-plugin-rtl';
import * as React from 'react';

import { disableAnimations } from './disableAnimations';
import { generateFontSource, getFontLocals, toCSSString } from './fontUtils';
import { invokeKeyframes } from './invokeKeyframes';

export function createEmotionRenderer(target?: Document): Renderer {
  const cacheLtr = createCache({
    container: target?.head,
    key: 'fui',
    stylisPlugins: [focusVisiblePlugin],

    // TODO: make this configurable via perf flags
    speedy: true,
  }) as EmotionCache & { insert: Function };
  const cacheRtl = createCache({
    container: target?.head,
    key: 'rfui',
    stylisPlugins: [focusVisiblePlugin, rtlPlugin],

    // TODO: make this configurable via perf flags
    speedy: true,
  });

  const sheet = new StyleSheet({
    key: `${cacheLtr.key}-global`,
    nonce: cacheLtr.sheet.nonce,
    container: cacheLtr.sheet.container,
  });

  const Provider: React.FC = props => {
    // TODO: Find a way to cleanup global styles
    // React.useEffect(() => {
    // return () => sheet.flush();
    // });

    return <>{props.children}</>;
  };

  const renderRule: RendererRenderRule = (styles, param) => {
    // Emotion has a bug with passing empty objects, should be fixed in upstream
    if (Object.keys(styles).length === 0) {
      return '';
    }

    const cache = param.direction === 'ltr' ? cacheLtr : cacheRtl;
    const style = param.disableAnimations ? disableAnimations(styles) : styles;
    const serialized = serializeStyles([invokeKeyframes(cache, style) as any], cache.registered, undefined);

    insertStyles(cache, serialized, true);

    return `${cache.key}-${serialized.name}`;
  };

  const renderGlobal: RendererRenderGlobal = (styles, selector) => {
    if (typeof styles === 'string') {
      const serializedStyles = serializeStyles(
        [styles],
        // This looks as a bug in typings as in Emotion code this function can be used with a single param.
        // https://github.com/emotion-js/emotion/blob/a076e7fa5f78fec6515671b78801cfc9d6cf1316/packages/core/src/global.js#L45
        // @ts-ignore
        undefined,
      );

      cacheLtr.insert(``, serializedStyles, sheet, false);
    }

    if (typeof styles === 'object') {
      if (typeof selector !== 'string') {
        throw new Error('A valid "selector" is required when an object is passed to "renderGlobal"');
      }

      const serializedStyles = serializeStyles(
        [{ [selector]: (styles as unknown) as ObjectInterpolation<{}> }],
        // This looks as a bug in typings as in Emotion code this function can be used with a single param.
        // https://github.com/emotion-js/emotion/blob/a076e7fa5f78fec6515671b78801cfc9d6cf1316/packages/core/src/global.js#L45
        // @ts-ignore
        null,
      );

      cacheLtr.insert(``, serializedStyles, sheet, false);
    }
  };
  const renderFont: RendererRenderFont = font => {
    const { localAlias, ...otherProperties } = font.props;

    const fontLocals = getFontLocals(localAlias);
    const fontFamily = toCSSString(font.name);

    renderGlobal(
      {
        ...otherProperties,
        src: generateFontSource(font.paths, fontLocals),
        fontFamily,
      },
      '@font-face',
    );
  };

  return {
    renderGlobal,
    renderFont,
    renderRule,

    Provider,
  };
}
