import { useCallback, useEffect, useRef, useState } from 'react';
import { toError } from '@theorchard/songwhip-utils';
import mitt from 'mitt';

import type { CreateFlowEmitter, CreateFlowStageDefinition } from './types';

import Box from '~/src/components/Box';
import { DialogBoxHeader } from '~/src/components/DialogBox/DialogBoxHeader';
import { useAppAlert } from '~/src/components/NextApp/lib/CoreUi';
import PageLoading from '~/src/components/PageLoading';
import Sticky from '~/src/components/Sticky';
import Text from '~/src/components/Text';
import { useListenOnEvent } from '~/src/hooks/useListenOnEvent';
import { useI18n } from '~/src/lib/i18n';
import { useAppRouter } from '~/src/lib/router2';
import { reportError } from '~/src/lib/sentry';
import darkTheme from '~/src/lib/theme/dark';
import { Button } from '~/src/ui/primitives/button';
import {
  generateFlowId,
  getFlowState,
  removeFlowState,
  setFlowState,
} from './storage';

export interface CreateFlowProps<T> {
  hideNextButton?: boolean;
  stages: CreateFlowStageDefinition<T>[];
  defaultData?: T;
  onSubmit: (data: T) => Promise<void>;
  onStageComplete?: (params: {
    stageId: string;
    data: T;
    events: CreateFlowEmitter<T>;
  }) => void;
}

/**
 * CreateFlow component that manages the flow of stages in a page creation process.
 * State is persisted in localStorage keyed by a flow ID stored in the URL query.
 */
export function CreateFlow<T extends object>({
  stages,
  onSubmit,
  onStageComplete,
  hideNextButton,
  defaultData,
}: CreateFlowProps<T>) {
  const router = useAppRouter();
  const query = router.getQuery();
  const emitter = useRef<CreateFlowEmitter<T>>(mitt());
  const [isSubmitting, setIsSubmitting] = useState(false);
  const { t } = useI18n();
  const appAlert = useAppAlert();

  // Stable flow ID — generate once and persist in a ref so it survives
  // re-renders before the URL is updated.
  const flowIdRef = useRef((query.flowId as string) || generateFlowId());
  const flowId = (query.flowId as string) || flowIdRef.current;

  // Initialize flow: set query param and persist defaultData for new flows.
  // Reset to first stage if user navigates back after submit (stale flow).
  useEffect(() => {
    const { isNew } = getFlowState(flowId);
    const activeStageId = query.stage as string | undefined;

    if (!query.flowId) {
      // New flow — persist defaultData and set the flowId in the URL
      if (defaultData) {
        setFlowState(flowId, defaultData);
      }
      router.setQuery({ ...query, flowId }, { reset: true });
      return;
    }

    if (activeStageId && isNew) {
      // Stale flow (e.g. back button after submit) — reset
      const newFlowId = generateFlowId();
      flowIdRef.current = newFlowId;

      if (defaultData) {
        setFlowState(newFlowId, defaultData);
      }

      router.setQuery({ flowId: newFlowId }, { reset: true });
    }
  }, [query.flowId, query.stage]);

  const { data: flowData } = getFlowState<T>(flowId);
  const activeStageId = query.stage as string | undefined;

  const activeStage =
    stages.find((stage) => stage.id === activeStageId) ?? stages[0];

  const activeStageIndex = stages.findIndex((s) => s.id === activeStage.id);
  const prevStage = stages[activeStageIndex - 1];
  const nextStage = stages[activeStageIndex + 1];

  const updateFlowData = useCallback(
    async (data: T, nextStageId?: string) => {
      const merged = { ...flowData, ...data };
      setFlowState(flowId, merged);

      await router.setQuery(
        { flowId, stage: nextStageId ?? activeStage.id },
        { reset: true }
      );
    },
    [flowId, flowData, activeStage.id, router]
  );

  useListenOnEvent(emitter.current, 'stageComplete', async (data) => {
    if (isSubmitting) return;

    await updateFlowData(data, nextStage?.id ?? activeStage.id);

    onStageComplete?.({
      stageId: activeStageId ?? activeStage.id,
      data,
      events: emitter.current,
    });
  });

  useListenOnEvent(emitter.current, 'persist', async (data) => {
    if (isSubmitting) return;

    await updateFlowData(data, activeStage.id);
  });

  useListenOnEvent(emitter.current, 'submit', async (data) => {
    if (isSubmitting) return;

    setIsSubmitting(true);

    const merged = { ...flowData, ...data };
    setFlowState(flowId, merged);

    try {
      await onSubmit(merged);
      removeFlowState(flowId);
    } catch (error) {
      appAlert({
        title: t('create.errorCreatingPage'),
        content: toError(error).message,
      });

      reportError({ error });
      setIsSubmitting(false);
    }
  });

  if (isSubmitting) return <PageLoading text={t('create.creatingPage')} />;

  return (
    <Box zIndex={1}>
      <Sticky
        zIndex={1}
        top="0"
        style={{ backgroundColor: 'rgba(17,17,17,0.9)' }}
      >
        {activeStage.HeaderComponent ? (
          <activeStage.HeaderComponent data={flowData} />
        ) : (
          <DialogBoxHeader
            background="none"
            zIndex={1}
            onBackClick={async () => {
              if (prevStage) {
                await router.setQuery(
                  { flowId, stage: prevStage.id },
                  { reset: true }
                );
              } else {
                removeFlowState(flowId);
                await router.push('/create');
              }
            }}
            renderMiddle={() => (
              <Text
                positionRelative
                top="50%"
                style={{ transform: 'translateY(-50%)' }}
                maxWidth="60%"
                margin="auto"
                size="1.45rem"
                isBold
                centered
                noSelection
                withEllipsis
                color={darkTheme.textColor60}
              >
                {activeStage.title}
              </Text>
            )}
            renderRight={
              hideNextButton || activeStage.hideNextButton
                ? undefined
                : () => (
                    <Button
                      variant="text"
                      testId="nextButton"
                      onClick={() => emitter.current.emit('nextButtonPressed')}
                    >
                      {nextStage
                        ? t('app.actions.next')
                        : t('app.actions.create')}
                    </Button>
                  )
            }
          />
        )}
      </Sticky>
      <Box padding="2rem" centerContent>
        <activeStage.Component events={emitter.current} data={flowData} />
      </Box>
    </Box>
  );
}
