/** @jsxRuntime classic */
/** @jsx jsx */

import { FC, useEffect, useMemo, useRef, useState } from 'react';
import { jsx, useTheme } from '@emotion/react';
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import { RecoilRoot } from 'recoil';
import { DndProvider } from './atoms/DndProvider';
import {
  MiddlewareContextProvider,
  useMiddlewareContext,
} from './hooks/useMiddlewareContext';
import { ECalendarMode, ECalendarView, ICalendarProps } from './types';
import {
  bodyContainer,
  bodyContent,
  cssProvider,
  rootContainer,
  dndProviderContainer,
} from './styles';
import { spaceHeight, spaceWidth } from './atoms/styles';
import { DailyViewVertical } from './atoms/daily/DailyViewVertical';
import { WeeklyView } from './atoms/weekly/WeeklyView';
import { oneHourHeight, oneHourWidth } from './utils/timeUtils';
import { HeaderContainer } from './atoms/HeaderContainer';
import { AsideContainer } from './atoms/AsideContainer';
import { DailyViewHorizontal } from './atoms/daily/horizontal/DailyViewHorizontal';
import {
  filterSlotsForDailyView,
  filterSlotsForOldSlots,
  getBodyContainerHeight,
  scrollToHorizontal,
  scrollToVertical,
} from './utils';
import { MonthlyView } from './atoms/monthly/MonthlyView';
import { usePartialRender } from './hooks/usePartialRender';

dayjs.extend(customParseFormat);

const isSafari =
  navigator.vendor &&
  navigator.vendor.indexOf('Apple') > -1 &&
  navigator.userAgent &&
  navigator.userAgent.indexOf('CriOS') == -1 &&
  navigator.userAgent.indexOf('FxiOS') == -1;

const Calendar: FC<ICalendarProps> = ({
  spaces,
  widthChangeTrigger,
  slots,
  EventComponent,
  DragOverlayComponent,
  ResourceGroupHeader,
  onChange,
  options = {},
  resourceId,
}) => {
  const colors = useTheme();
  const { mode = ECalendarMode.DAILY, view = ECalendarView.VERTICAL, date } =
    options || {};
  const horizontal = view === ECalendarView.HORIZONTAL;

  const [width, setWidth] = useState(200);

  const {
    day: daysCount,
    incrementDay,
    setOptions,
    setPartialRanges,
    initiateEventsToSpaces,
    getTopAndHeight,
  } = useMiddlewareContext();

  const headerRef = useRef<HTMLDivElement>(null);
  const bodyRef = useRef<HTMLDivElement>(null);
  const verticalSpacesRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    setOptions(options);
  }, [options]);

  const { from: minVertical, to: maxVertical } = usePartialRender({
    ref: bodyRef,
    elementSize: view == ECalendarView.VERTICAL ? oneHourHeight : spaceHeight,
    disable: mode !== ECalendarMode.DAILY || !options?.partialRendering,
  });
  const { from: minHorizontal, to: maxHorizontal } = usePartialRender({
    ref: bodyRef,
    elementSize: view == ECalendarView.VERTICAL ? spaceWidth : oneHourWidth,
    disable: mode !== ECalendarMode.DAILY || !options?.partialRendering,
    direction: 'horizontal',
  });

  const {
    horizontalFrom,
    horizontalTo,
    verticalFrom,
    verticalTo,
  } = useMemo(() => {
    const columnsCount = spaces.reduce(
      (sum, item) => sum + (item.children.length || 1),
      0
    );
    const difference = columnsCount - spaces.length;

    const isVerticalView = view === ECalendarView.VERTICAL;

    const spaceTo = (isVerticalView ? maxHorizontal : maxVertical) - difference;
    let spaceFrom = (isVerticalView ? minHorizontal : minVertical) - difference;
    if (spaceFrom < 0) spaceFrom = 0;

    const hoursFrom = isVerticalView ? minVertical : minHorizontal;
    const hoursTo = isVerticalView ? maxVertical : maxHorizontal;

    if (isVerticalView)
      return {
        horizontalFrom: spaceFrom,
        horizontalTo: spaceTo,
        verticalFrom: hoursFrom,
        verticalTo: hoursTo,
      };
    else
      return {
        horizontalFrom: hoursFrom,
        horizontalTo: hoursTo,
        verticalFrom: spaceFrom,
        verticalTo: spaceTo,
      };
  }, [spaces, minHorizontal, maxHorizontal, minVertical, maxVertical, view]);

  useEffect(() => {
    setPartialRanges({
      vertical: { from: verticalFrom, to: verticalTo },
      horizontal: { from: horizontalFrom, to: horizontalTo },
    });
  }, [horizontalFrom, horizontalTo, verticalFrom, verticalTo]);

  useEffect(() => {
    if (slots && slots?.eventsToSpaces) {
      if (mode === ECalendarMode.DAILY && !options?.infiniteScrolling) {
        initiateEventsToSpaces({
          events: slots.events,
          eventsToSpaces: filterSlotsForDailyView(
            slots.eventsToSpaces,
            options?.date
          ),
        });
      } else if (mode === ECalendarMode.DAILY) {
        initiateEventsToSpaces({
          events: slots.events,
          eventsToSpaces: filterSlotsForOldSlots(
            slots.eventsToSpaces,
            options?.date
          ),
        });
      } else {
        initiateEventsToSpaces(slots);
      }
    }
  }, [slots, mode, view, options?.date, options?.infiniteScrolling]);

  useEffect(() => {
    let resizeObserver: ResizeObserver;

    if (bodyRef?.current) {
      resizeObserver = new ResizeObserver((data) => {
        setWidth(data?.[0]?.contentRect?.width || 0);
      });
      resizeObserver.observe(bodyRef?.current);
    }

    return () => {
      resizeObserver.disconnect();
    };
  }, [view, options?.hourSize, bodyRef?.current]);

  useEffect(() => {
    const handleScroll = (e: Event) => {
      const body = e.target as Element;

      if (
        headerRef.current &&
        headerRef.current.scrollLeft !== body.scrollLeft
      ) {
        headerRef.current.scrollLeft = body.scrollLeft;
      }
      if (
        verticalSpacesRef.current &&
        verticalSpacesRef.current.scrollTop !== body.scrollTop
      ) {
        verticalSpacesRef.current.scrollTop = body.scrollTop;
      }

      if (options.infiniteScrolling && mode === ECalendarMode.DAILY) {
        let scrollPosition = 0;
        let frontierPosition = 0;

        if (view === ECalendarView.HORIZONTAL) {
          scrollPosition = body.scrollLeft + body.clientWidth;
          frontierPosition = body.scrollWidth * 0.8;
        } else {
          scrollPosition = body.scrollTop + body.clientHeight;
          frontierPosition = body.scrollHeight * 0.8;
        }

        if (scrollPosition > frontierPosition) {
          incrementDay();
        }
      }
    };

    bodyRef?.current?.addEventListener('scroll', handleScroll, false);

    return () => {
      bodyRef?.current?.removeEventListener('scroll', handleScroll);
    };
  }, [
    headerRef,
    bodyRef,
    daysCount,
    options,
    verticalSpacesRef,
    mode,
    view,
    date,
  ]);

  const scrollToHour = (now: number) => {
    if (mode === ECalendarMode.DAILY && bodyRef.current) {
      if (isSafari) {
        if (horizontal) {
          scrollToHorizontal(bodyRef.current, now * oneHourWidth);
          scrollToVertical(bodyRef.current, 0);
        } else {
          scrollToHorizontal(bodyRef.current, 0);
          scrollToVertical(bodyRef.current, now * oneHourHeight);
        }
      } else {
        if (horizontal) {
          bodyRef.current.scrollTo(now * oneHourWidth, 0);
        } else {
          bodyRef.current.scrollTo(0, now * oneHourHeight);
        }
      }
    }
  };

  const scrollTo = () => {
    const { resourceId, startTime, endTime } = options?.scrollTo || {};
    const body = bodyRef?.current;

    if (!body) return;

    const resource = document.getElementById(`resource_${resourceId}`);

    let resourcePosition = 0;
    if (resource) {
      if (horizontal) {
        resourcePosition = resource.offsetTop;
      } else {
        resourcePosition = resource.offsetLeft + resource.clientWidth / 2;
      }
    }

    let hoursPosition = 0;
    if (startTime && endTime) {
      const date = dayjs.utc(options?.date).format('DD/MM/YYYY');
      const parseFormat = 'DD/MM/YYYY HH:mm';

      const start = dayjs.utc(`${date} ${startTime}`, parseFormat);
      let end = dayjs.utc(`${date} ${endTime}`, parseFormat);
      if (dayjs.utc(start).diff(end, 'millisecond') >= 0) {
        end = end.add(1, 'day');
      }

      const { top, height } = getTopAndHeight(
        start.toDate().getTime(),
        end.toDate().getTime(),
        horizontal
      );

      hoursPosition = top + height / 2;
      if (!horizontal) hoursPosition += height / 2;
    }

    const halfBodyWidth = body.clientWidth / 2;
    const halfHoursHeight = body.clientHeight / 2;

    if (isSafari) {
      if (horizontal) {
        if (hoursPosition) {
          scrollToHorizontal(body, hoursPosition - halfBodyWidth);
        }
        if (resourcePosition) {
          scrollToVertical(body, resourcePosition - halfHoursHeight);
        }
      } else {
        if (resourcePosition) {
          scrollToHorizontal(body, resourcePosition - halfBodyWidth);
        }
        if (hoursPosition) {
          scrollToVertical(body, hoursPosition - halfHoursHeight);
        }
      }
    } else {
      if (horizontal) {
        if (hoursPosition && resourcePosition) {
          body.scrollTo(
            hoursPosition - halfBodyWidth,
            resourcePosition - halfHoursHeight
          );
        } else if (hoursPosition) {
          body.scrollTo(hoursPosition - halfBodyWidth, body.scrollTop);
        } else if (resourcePosition) {
          body.scrollTo(body.scrollLeft, resourcePosition - halfHoursHeight);
        }
      } else {
        if (hoursPosition && resourcePosition) {
          body.scrollTo(
            resourcePosition - halfBodyWidth,
            hoursPosition - halfHoursHeight
          );
        } else if (resourcePosition) {
          body.scrollTo(resourcePosition - halfBodyWidth, body.scrollTop);
        } else if (hoursPosition) {
          body.scrollTo(body.scrollLeft, hoursPosition - halfHoursHeight);
        }
      }
    }
  };

  useEffect(() => {
    scrollTo();
  }, [options?.scrollTo]);

  useEffect(() => {
    if (options?.mode === ECalendarMode.DAILY) {
      const now = Number(dayjs.utc().format('HH'));
      scrollToHour(now);
    }
  }, [options?.mode, options?.view]);

  const bodyContainerHeight = getBodyContainerHeight(spaces, mode, horizontal);

  return (
    <div css={cssProvider(colors, spaceWidth, mode === ECalendarMode.MONTHLY)}>
      <div css={rootContainer}>
        <AsideContainer
          bodyContainerHeight={bodyContainerHeight}
          verticalSpacesRef={verticalSpacesRef}
          width={width}
          spaces={spaces}
          ResourceGroupHeader={ResourceGroupHeader}
        />
        <DndProvider
          DragOverlayComponent={DragOverlayComponent}
          onChange={onChange}
        >
          <div css={dndProviderContainer(colors)} ref={bodyRef}>
            <HeaderContainer
              headerRef={headerRef}
              width={width}
              spaces={spaces}
              ResourceGroupHeader={ResourceGroupHeader}
              resourceId={resourceId}
            />
            <div id="bodyContainer" css={bodyContainer(horizontal)}>
              <div
                css={bodyContent(
                  colors,
                  bodyContainerHeight * (!horizontal ? daysCount : 1),
                  horizontal
                )}
              >
                {mode === ECalendarMode.DAILY &&
                  (horizontal ? (
                    <DailyViewHorizontal
                      spaces={spaces}
                      EventComponent={EventComponent}
                      onAddNewSlotClick={onChange}
                    />
                  ) : (
                    <DailyViewVertical
                      spaces={spaces}
                      EventComponent={EventComponent}
                      onAddNewSlotClick={onChange}
                    />
                  ))}
                {mode === ECalendarMode.WEEKLY && (
                  <WeeklyView spaces={spaces} EventComponent={EventComponent} />
                )}
                {mode === ECalendarMode.MONTHLY && (
                  <MonthlyView
                    id={String(resourceId || spaces[0]?.id)}
                    EventComponent={EventComponent}
                  />
                )}
              </div>
            </div>
          </div>
        </DndProvider>
      </div>
    </div>
  );
};

export const SACalendar = (props: ICalendarProps) => (
  <RecoilRoot>
    <MiddlewareContextProvider onChange={props.onChange}>
      <Calendar {...props} />
    </MiddlewareContextProvider>
  </RecoilRoot>
);
