import { useCallback, useMemo } from 'react';
import Debug from 'debug';

import type { QueryParams } from '~/lib/utils/url';
import type { ReactNode } from 'react';
import type { DialogBoxProps, DialogBoxRenderContent } from '../DialogBox';

import useHash from '~/src/hooks/useHash';
import DialogBox from '../DialogBox';

const debug = Debug('songwhip/DialogBoxWithStages-router');
const DEFAULT_STAGE = 'default';

export type SetStage<TStageNames extends string> = (
  stage: TStageNames,
  options?: {
    params?: QueryParams;
    options?: { replace?: boolean; reset?: boolean };
  }
) => void;

export type RenderStage<T extends string> = (
  params: Parameters<DialogBoxRenderContent<any>>[0] & {
    setStage: SetStage<T>;
    stage: string;
  }
) => ReactNode;

/**
 * A DialogBox variant that supports rendering of stages and
 * smarter history management so that browser back/forward
 * buttons work more as expected.
 */
const DialogBoxWithStages = <
  TStageNames extends 'default' | string,
  TRenderStage = RenderStage<TStageNames>,
>({
  dialogId,
  initialStage = DEFAULT_STAGE,
  onClose,
  stages,
  ...dialogBoxProps
}: Omit<DialogBoxProps, 'onClose' | 'renderContent'> & {
  dialogId: string;
  initialStage?: string;
  onClose?: () => void;
  stages: Record<TStageNames, TRenderStage> & { default: TRenderStage };
}) => {
  const { setHashParam, hashParams, backToBeforeFirstHash, getPrevHashParams } =
    useHash();

  const currentStage = hashParams[dialogId] || DEFAULT_STAGE;

  const setStage: SetStage<TStageNames> = (stage, { params, options } = {}) => {
    const prevHashParams = getPrevHashParams();
    const prevStage = prevHashParams[dialogId];
    const isPrev = prevStage === stage;

    debug('set stage', { stage, prevHashParams, prevStage, isPrev });

    // when the stage is the previous one and we do not add/update params
    // we can call back
    if (isPrev && params === undefined) {
      history.back();
      debug("go back to stage: '%s'", stage);
      return;
    }

    setHashParam(
      {
        [dialogId]: stage,
        ...params,
      },
      options
    );
  };

  return (
    <DialogBox
      {...dialogBoxProps}
      onClose={useCallback(() => {
        if (onClose) onClose();

        debug('on close');

        backToBeforeFirstHash();
      }, [onClose, currentStage])}
      renderContent={useCallback(
        (params) => {
          debug('render stage: %s', currentStage);

          const renderStage = stages[currentStage];

          if (!renderStage) {
            throw new Error(`unknown stage ${currentStage}`);
          }

          return renderStage({
            setStage,
            stage: currentStage,
            ...params,
          });
        },
        [stages, currentStage]
      )}
    />
  );
};

export const useOpenDialog = (dialogId: string) => {
  const { setHashParam, hasHashParam } = useHash();
  const isOpen = hasHashParam(dialogId);

  const open = (
    stage = DEFAULT_STAGE,
    {
      params,
      replace,
      reset,
    }: { params?: QueryParams; replace?: boolean; reset?: boolean } = {}
  ) =>
    setHashParam(
      {
        [dialogId]: stage,
        ...params,
      },
      { replace, reset }
    );

  return useMemo<[typeof isOpen, typeof open]>(() => [isOpen, open], [isOpen]);
};

export default DialogBoxWithStages;
