import * as React from 'react';
import {
  memo, FC, useCallback, useState, useLayoutEffect, useRef,
} from 'react';
import { style } from 'typestyle';
import { createPortal } from 'react-dom';
import usePortal from '../../hooks/usePortal';
import useResponsive from '../../hooks/useResponsive';
import Carousel from '../Layouts/Carousel';
import Wall from '../Layouts/Wall';
import { Mosaic } from '../Layouts/Mosaic';
import PostViewer from '../PostViewer';
import { useStore } from '../../services/store';
import { IMedia } from '../../types';
import { Thumbnail } from '../Thumbnail';
import Visibility from '../Visibility';
import { Helpers } from '../../services/helpers';
import useWidgetObserved from '../../hooks/useWidgetObserved';

interface WidgetProps {
  root: HTMLElement;
  startDate: Date;
  postViewerPortalId?: string;
}

const Widget: FC<WidgetProps> = memo(({ root, startDate, postViewerPortalId }) => {
  const store = useStore();
  const [hasLoaded, setHasLoaded] = useState(false);
  const { setStoreState } = store;

  const portalElement = usePortal(postViewerPortalId, !!store.post);

  const widgetDivRef = useRef<HTMLDivElement | null>(null);
  useWidgetObserved(widgetDivRef);

  // Measure the widget width before the first paint so carousel dimension
  // calculations use the real value.
  useLayoutEffect(() => {
    const width = Math.floor(root.getBoundingClientRect().width);
    setStoreState((state) => {
      const isMobile = state.forceMobile || Helpers.isMobileDevice();
      if (state.widgetWidth === width && state.isMobile === isMobile) return state;
      return { ...state, isMobile, widgetWidth: width };
    });
  }, [root, setStoreState]);

  // Stable callback so useResponsive doesn't disconnect/reconnect the
  // ResizeObserver on every render.
  const onResize = useCallback(
    (width: number) => {
      setStoreState((state) => {
        const isMobile = state.forceMobile || Helpers.isMobileDevice();
        if (state.widgetWidth === width && state.isMobile === isMobile) return state;
        return { ...state, isMobile, widgetWidth: width };
      });
    },
    [setStoreState],
  );

  useResponsive(root, onResize);

  // Event handlers
  const handlePostChange = useCallback(
    (post: IMedia | null) => {
      if (post) {
        store.setStoreState((state) => ({ ...state, post }));
        Helpers.replacePostInHistory(post);
      }
    },
    [store],
  );

  const widgetLoaded = useCallback(() => {
    if (hasLoaded) return;

    const elapsedSeconds = startDate
      ? (new Date().getTime() - startDate.getTime()) / 1000
      : undefined;

    setHasLoaded(true);
    store.triggerEvent(
      'widgetLoaded',
      {
        elapsedSeconds,
        posts: store.data.content.medias,
        layout: store.data.settings.type,
      },
      store.data.id,
    );
  }, [hasLoaded, startDate, store]);

  const widgetLoadingFailed = useCallback(() => {
    if (hasLoaded) return;
    setHasLoaded(true);
    store.triggerEvent('widgetLoadingFailed', {}, store.data.id);
  }, [hasLoaded, store]);

  const mediaRender = useCallback(
    (media: IMedia, position: number) => <Thumbnail media={media} position={position} />,
    [],
  );

  if (!store.data) {
    widgetLoadingFailed();
    return null;
  }

  widgetLoaded();

  const currentType = store.data.settings.type || 'wall';

  return (
    <Visibility>
      {(ref) => (
        <div className={widgetClass} ref={(node) => { ref(node); widgetDivRef.current = node; }}>
          <div
            className={`${store.config.classPrefix}-container`}
            aria-hidden={store.post ? 'true' : undefined}
            {...(store.post ? { inert: '' } : {})}
          >
            {
              {
                carousel: (
                  <Carousel
                    items={store.data.content.medias.map((m, i) => mediaRender(m, i))}
                    onPostChange={handlePostChange}
                  />
                ),
                mosaic: <Mosaic />,
                wall: <Wall forceColumns={store.data.settings.forceColumns} />,
              }[currentType]
            }
          </div>
          {portalElement
          && store.post
          && createPortal(<PostViewer onPostChange={handlePostChange} />, portalElement)}
        </div>
      )}
    </Visibility>
  );
});

const widgetClass = style({
  all: 'initial',
  $nest: {
    '*': {
      boxSizing: 'initial',
    },
  },
});

export default Widget;
