import { createSelector } from '@reduxjs/toolkit';
import React, {
  useEffect,
  useRef,
  useState,
  useCallback,
  ReactNode,
  useMemo,
} from 'react';
import { useDispatch, useSelector } from '../../app/store';
import {
  depersistRendererSlice,
  deserializeRendererSlice,
  selectPersistentRendererSlice,
  serializeRendererSlice,
} from '../canvas/rendererSlice';
import {
  depersistFileSlice,
  deserializeFileSlice,
  selectPersistentFileSlice,
  serializeFileSlice,
} from '../loader/fileSlice';
import useRefWithLatest from '../../hooks/useRefWithLatest';
import { HistoryContext } from './HistoryContext';

const DEBOUNCE_TIME = 250;

class HistoryState {
  constructor(
    public file: ReturnType<typeof selectPersistentFileSlice>,
    public renderer: ReturnType<typeof selectPersistentRendererSlice>
  ) {}
}

const selectPersistent = createSelector(
  selectPersistentFileSlice,
  selectPersistentRendererSlice,
  (file, renderer) => new HistoryState(file, renderer)
);
const serialize = (persistent: ReturnType<typeof selectPersistent>) => {
  const params = new URLSearchParams();
  serializeFileSlice(persistent.file, params);
  serializeRendererSlice(persistent.renderer, params);
  const url = new URL(window.location.href);
  url.search = params.toString();
  return url.href;
};
const depersist = (
  persistent: ReturnType<typeof selectPersistent>,
  dispatch: ReturnType<typeof useDispatch>
) => {
  depersistFileSlice(persistent.file, dispatch);
  depersistRendererSlice(persistent.renderer, dispatch);
};
const deserialize = (
  params: URLSearchParams,
  dispatch: ReturnType<typeof useDispatch>
) => {
  deserializeFileSlice(params, dispatch);
  deserializeRendererSlice(params, dispatch);
};

export function HistoryManager({ children }: { children?: ReactNode }) {
  const [ready, setReady] = useState(false);
  const persistent = useSelector(selectPersistent);
  const persistentRef = useRefWithLatest(persistent);
  const lastHistoryUpdateTimeRef = useRef<number>();
  const nextHistoryUpdateTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
  const dispatch = useDispatch();

  const onPopState = useCallback(() => {
    setReady(false);
    if (window.history.state instanceof HistoryState) {
      depersist(window.history.state, dispatch);
    } else {
      deserialize(new URLSearchParams(window.location.search), dispatch);
    }
    setReady(true);
  }, [dispatch]);
  const navigate = useCallback(
    (url: string, replace = false) => {
      if (replace) {
        window.history.replaceState(undefined, document.title, url);
      } else {
        window.history.pushState(undefined, document.title, url);
      }
      onPopState();
    },
    [onPopState]
  );

  useEffect(() => {
    onPopState();
    window.addEventListener('popstate', onPopState);
    return () => window.removeEventListener('popstate', onPopState);
  }, [onPopState]);

  useEffect(() => {
    if (!ready || nextHistoryUpdateTimeoutRef.current !== undefined) {
      return;
    }

    const updateHistory = () => {
      window.history.replaceState(
        persistentRef.current,
        document.title,
        serialize(persistentRef.current)
      );
      lastHistoryUpdateTimeRef.current = performance.now();
      nextHistoryUpdateTimeoutRef.current = undefined;
    };

    const lastHistoryUpdateTime = lastHistoryUpdateTimeRef.current;
    const currentTime = performance.now();
    const timeDelta =
      lastHistoryUpdateTime !== undefined
        ? currentTime - lastHistoryUpdateTime
        : Infinity;
    if (timeDelta >= DEBOUNCE_TIME) {
      updateHistory();
    } else {
      nextHistoryUpdateTimeoutRef.current = setTimeout(
        updateHistory,
        DEBOUNCE_TIME - timeDelta
      );
    }
  }, [ready, persistent, persistentRef]);
  useEffect(
    () => () => {
      if (nextHistoryUpdateTimeoutRef.current !== undefined) {
        clearTimeout(nextHistoryUpdateTimeoutRef.current);
        nextHistoryUpdateTimeoutRef.current = undefined;
      }
    },
    []
  );

  return (
    <HistoryContext.Provider value={useMemo(() => ({ navigate }), [navigate])}>
      {children}
    </HistoryContext.Provider>
  );
}
