import { captureException } from '@sentry/nextjs';
import { useMutation } from '@tanstack/react-query';
import { clamp } from 'lodash-es';
import { observer } from 'mobx-react-lite';
import { useRouter } from 'next/navigation';
import {
  createContext,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';

import { useStores } from '@/app/(root)/AppProviders';
import Button, {
  ButtonShape,
  ButtonSize,
  ButtonVariant,
} from '@/components/button/Button';
import ImageWithFallback from '@/components/image/ImageWithFallback';
import { ModalTypes } from '@/components/modal/constants/ModalTypes';
import SpinnerSVG from '@/components/svg/SpinnerSVG';
import { toast } from '@/components/toast/Toast';
import { ToastV2Props } from '@/components/toast/ToastV2';
import { HOOK_STATUS, useHookById } from '@/hooks/useHooks';
import { useVideoUpload } from '@/hooks/useVideoUpload';
import { CheckIcon } from '@/icons';
import { useApiClient } from '@/lib/apiClient';
import { components } from '@/lib/gen';
import logWebUserEvent from '@/logging/logWebUserEvent';
import { Clip } from '@/state/clipStore';
import { generateLinkUrl } from '@/utils/embeds';

/**
 * Video files that are larger than this will be rejected by the client before
 * they are uploaded.
 */
export const MAX_VIDEO_UPLOAD_FILESIZE_MB = 200;

// Used for validation before upload
export const MIN_VIDEO_DURATION_SECONDS = 10;
export const MAX_VIDEO_DURATION_SECONDS = 241;

// Used for trimming/alignment UI
export const MIN_DURATION_MS = 10 * 1000;
export const MAX_DURATION_MS = 241 * 1000;

/**
 * Maximum desync allowed between the audio and video during playback.
 * This is typically not an issue in practice.
 */
export const MAX_MEDIA_DESYNCED_MS = 200;

const UPLOAD_TIMEOUT = 10 * 60 * 1000; // 10 minutes in milliseconds
/**
 * Calculates the full media state from the minimal edit state
 */
function getDerivedMediaState<
  S extends {
    videoStart: number;
    videoEnd: number;
    audioStart: number;
    current: number;
    audioViewportWidth: number;
    minDuration: number;
    maxDuration: number;
    videoDuration: number;
    audioDuration: number;
  },
>(state: S) {
  const duration = state.videoEnd - state.videoStart;
  const audioRangePadding =
    0.5 * Math.max(0, state.audioViewportWidth - duration);
  return {
    ...state,
    duration,
    audioEnd: state.audioStart + duration,
    audioViewportStart: state.audioStart - audioRangePadding,
    audioViewportEnd: state.audioStart + duration + audioRangePadding,
    videoViewportStart: 0,
    videoViewportEnd: state.videoDuration,
  };
}

export function useMediaEditState(options: {
  /**
   * Full duration of audio track (ms)
   */
  audioDuration: number;
  /**
   * Full duration of video track (ms)
   */
  videoDuration: number;
  /**
   * Minimum trimmed hook size
   */
  minDuration?: number;
  /**
   * Maximum trimmed hook size
   */
  maxDuration?: number;

  defaultVideoStart?: number;
  defaultVideoEnd?: number;
  defaultAudioStart?: number;
  defaultPlayheadTime?: number;

  /**
   * Controls the effective "zoom" of the audio track
   */
  audioViewportWidth?: number;
}) {
  const {
    videoDuration,
    audioDuration,
    minDuration = Math.min(videoDuration, MIN_DURATION_MS),
    maxDuration = Math.min(audioDuration, videoDuration, MAX_DURATION_MS),
    defaultVideoStart = 0,
    defaultVideoEnd = Math.min(audioDuration, videoDuration),
    defaultAudioStart = 0,
    defaultPlayheadTime = defaultAudioStart,
    audioViewportWidth = Math.min(maxDuration * 2, audioDuration),
  } = options;

  // Parts of the state that the user can control
  const [state, setState] = useState(() =>
    getDerivedMediaState({
      videoStart: defaultVideoStart,
      videoEnd: defaultVideoEnd,
      audioStart: defaultAudioStart,
      current: defaultPlayheadTime,
      audioViewportWidth,
      minDuration,
      maxDuration,
      videoDuration,
      audioDuration,
    })
  );

  /**
   * Update state and calculate derived values
   */
  const updateState = useCallback(
    (
      stateUpdates:
        | Partial<
            Pick<
              typeof state,
              'videoStart' | 'videoEnd' | 'audioStart' | 'current'
            >
          >
        | ((prevState: typeof state) => typeof state)
    ) => {
      setState((prevState) =>
        getDerivedMediaState({
          ...prevState,
          ...(typeof stateUpdates === 'function'
            ? stateUpdates(prevState)
            : stateUpdates),
        })
      );
    },
    []
  );

  // Reset the state when the song or window size changes
  useEffect(() => {
    setState((prevState) => {
      let { videoStart, videoEnd, audioStart, current } = prevState;
      // Try to preserve current duration
      let duration = clamp(videoEnd - videoStart, minDuration, maxDuration);
      // Don't go beyond end of video
      videoEnd = clamp(
        videoEnd,
        duration,
        Math.min(audioDuration, videoDuration)
      );
      videoStart = Math.max(0, videoEnd - duration);
      // Actual duration might be different
      duration = videoEnd - videoStart;
      // Don't go beyond end of audio
      audioStart = clamp(audioStart, 0, audioDuration - duration);
      // Clamp playhead to trim range
      current = clamp(current, audioStart, audioStart + duration);

      return {
        ...prevState,
        videoStart,
        videoEnd,
        audioStart,
        current,
        audioViewportWidth,
        minDuration,
        maxDuration,
        videoDuration,
        audioDuration,
      };
    });
  }, [
    audioViewportWidth,
    minDuration,
    maxDuration,
    videoDuration,
    audioDuration,
  ]);

  const handleVideoRangeSet = useCallback(
    (start: number, end: number) => {
      updateState((prevState) => {
        const nextState = {
          ...prevState,
          videoStart: clamp(start, 0, prevState.videoDuration),
          videoEnd: clamp(end, 0, prevState.videoDuration),
        };
        if (end === prevState.videoEnd) {
          // End time unchanged: constrain start time
          nextState.videoStart = clamp(
            nextState.videoStart,
            prevState.videoEnd - prevState.maxDuration,
            prevState.videoEnd - prevState.minDuration
          );
        } else if (start === prevState.videoStart) {
          // Start time unchanged: constrain end time
          nextState.videoEnd = clamp(
            nextState.videoEnd,
            prevState.videoStart + prevState.minDuration,
            prevState.videoStart + prevState.maxDuration
          );
        }
        // Sanity check that audio start is still within range and adjust as needed
        const nextDuration = nextState.videoEnd - nextState.videoStart;
        if (nextState.audioStart + nextDuration > prevState.audioDuration) {
          nextState.audioStart = Math.max(
            0,
            prevState.audioDuration - nextDuration
          );
        }
        // Preserve current time relative to the previous state
        const prevHookTime = prevState.current - prevState.audioStart;
        const audioEnd = nextState.audioStart + nextDuration;
        nextState.current = Math.min(
          nextState.audioStart + prevHookTime,
          audioEnd
        );
        // Now we're good
        return nextState;
      });
    },
    [updateState]
  );

  const handleAudioRangeSet = useCallback(
    (start: number) => {
      updateState((prevState) => {
        const nextState = {
          ...prevState,
          audioStart: clamp(
            start,
            0,
            prevState.audioDuration - prevState.duration
          ),
        };
        nextState.audioEnd = nextState.audioStart + prevState.duration;
        // Preserve current time relative to the previous state
        const prevHookTime = prevState.current - prevState.audioStart;
        nextState.current = clamp(
          nextState.audioStart + prevHookTime,
          nextState.audioStart,
          nextState.audioEnd
        );
        return nextState;
      });
    },
    [updateState]
  );

  const handleCurrentTimeSet = useCallback(
    (value: number) => {
      updateState((prevState) => ({
        ...prevState,
        current: clamp(value, prevState.audioStart, prevState.audioEnd),
      }));
    },
    [updateState]
  );

  return {
    ...state,
    onVideoRangeSet: handleVideoRangeSet,
    onAudioRangeSet: handleAudioRangeSet,
    onCurrentTimeSet: handleCurrentTimeSet,
  };
}

type VideoHookCreationRequestPayload =
  components['schemas']['VideoHookCreationRequest'];

export const useCreateHookMutation = () => {
  const apiClient = useApiClient();

  return useMutation({
    mutationFn: async ({
      videoUploadID,
      videoFile,
      clipId,
      title,
      caption,
      allowComments,
      showLyrics,
      clipStartTimeMs,
      clipEndTimeMs,
      clipDurationMs,
      videoStartTimeMs,
      videoEndTimeMs,
      clipVolume = 100,
      videoVolume = 0,
      customThumbnailS3Id,
    }: {
      videoUploadID?: string | null;
      videoFile: File | null;
      clipId: string;
      title: string;
      caption?: string;
      allowComments: boolean;
      showLyrics: boolean;
      clipStartTimeMs: number;
      clipEndTimeMs: number;
      clipDurationMs: number;
      videoStartTimeMs: number;
      videoEndTimeMs: number;
      clipVolume?: number;
      videoVolume?: number;
      customThumbnailS3Id?: string | null;
    }) => {
      const hookAudioDurationMs = clipEndTimeMs - clipStartTimeMs;
      const hookVideoDurationMs = videoEndTimeMs - videoStartTimeMs;

      // Sanity check volume controls:
      // - Only one audio source allowed at a time
      // - Only support muted or full volume
      const sanitizedClipVolume = clipVolume || !videoVolume ? 100 : 0;
      const sanitizedVideoVolume = 100 - sanitizedClipVolume;

      if (!videoFile || !videoUploadID) {
        throw new Error('Please select a video file or wait for it to upload.');
      }

      if (!clipId) {
        throw new Error('Please select a song.');
      }

      // We only need to enforce these audio constraints if we're using the clip audio
      if (sanitizedClipVolume) {
        if (clipStartTimeMs >= clipEndTimeMs) {
          throw new Error(
            `Audio start time (${clipStartTimeMs}) must be less than audio end time (${clipEndTimeMs}).`
          );
        }

        if (clipStartTimeMs < 0 || clipEndTimeMs > clipDurationMs) {
          throw new Error(
            `Audio start time (${clipStartTimeMs}) and end time (${clipEndTimeMs}) must be within the song duration (0 - ${clipDurationMs}).`
          );
        }

        if (hookAudioDurationMs > MAX_DURATION_MS) {
          throw new Error(
            `Audio duration (${clipEndTimeMs - clipStartTimeMs}) must be less than 30 seconds.`
          );
        }

        // Leave a little bit of wiggle room
        if (Math.abs(hookVideoDurationMs - hookAudioDurationMs) > 100) {
          throw new Error(
            `Audio duration (${clipEndTimeMs - clipStartTimeMs}) must be equal to video duration (${videoEndTimeMs - videoStartTimeMs}).`
          );
        }
      }

      const videoObject: components['schemas']['VideoRenderVideoSchema'] = {
        upload_id: videoUploadID,
        source_start_time: videoStartTimeMs / 1000,
        source_end_time: videoEndTimeMs / 1000,
        volume: sanitizedVideoVolume,
      };

      const payload: VideoHookCreationRequestPayload = {
        clip_id: clipId,
        hook_publish: {
          title: title,
          caption: caption,
          allow_comments: allowComments,
          show_lyrics: showLyrics,
          custom_thumbnail_s3_id: customThumbnailS3Id,
          // everything is public for now
          // is_public: true,
        },
        video_render: {
          song: {
            song_id: clipId,
            clip_start_time: clipStartTimeMs / 1000,
            clip_end_time: clipEndTimeMs / 1000,
            volume: sanitizedClipVolume,
          },
          videos: [videoObject],
        },
      };

      const { data: creationResponse, response } = await apiClient.POST(
        '/api/video/hooks/create',
        {
          body: payload,
        }
      );

      if (response.status === 423) {
        throw new Error('Your account requires support assistance');
      }

      if (!creationResponse || !creationResponse.id) {
        captureException({
          message: 'Hooks creation failed',
          payload,
          response: creationResponse,
        });
        throw new Error(
          'Failed to create hook: Invalid response data from server.'
        );
      }

      return creationResponse;
    },
    onSuccess: (creationResponse) => {
      // Hook is going through processing...
      console.log('Hook creation successful:', creationResponse);
    },
    onError: (err) => {
      const errorMessage =
        err instanceof Error
          ? err.message
          : 'An unexpected error occurred during hook creation.';
      toast({
        title: `Exception during hook creation: ${errorMessage}`,
        status: 'error',
      });
      console.error('Exception during hook creation:', err);
    },
  });
};

export type HookFormScreenType =
  | 'selectSongInFlow'
  | 'uploadVideoInFlow'
  | 'selectSong'
  | 'uploadVideo'
  | 'postHook'
  | 'editHook';

export const useHookForm = () => {
  const { menus } = useStores();
  const [currentScreen, setCurrentScreen] =
    useState<HookFormScreenType>('selectSongInFlow');

  const [selectedSong, setSelectedSong] = useState<Clip | null>(null);
  const [selectedVideo, setSelectedVideo] = useState<File | null>(null);
  const [videoDuration, setVideoDuration] = useState(0);
  const [videoDimensions, setVideoDimensions] = useState([0, 0]);
  const [thumbnailTimestamp, setThumbnailTimestamp] = useState(0);
  const [customThumbnailS3Id, setCustomThumbnailS3Id] = useState<string | null>(
    null
  );
  const [title, setTitle] = useState('');
  const [caption, setCaption] = useState('');
  const [allowComments, setAllowComments] = useState(true);
  const [showLyrics, setShowLyrics] = useState(true);
  const router = useRouter();
  const [videoSound, setVideoSound] = useState(false);

  const {
    uploadId,
    uploadImageUrl,
    uploadProgress,
    uploadStatus,
    audioAlignmentTimestamp,
    removeFiles,
    updateSelectedVideo,
  } = useVideoUpload({
    requireVideoSprite: true,
    requireModeration: false,
    videoUploadType: 'video_hook',
    maxFileSizeMb: MAX_VIDEO_UPLOAD_FILESIZE_MB,
    minDuration: MIN_VIDEO_DURATION_SECONDS,
    maxDuration: MAX_VIDEO_DURATION_SECONDS,
    onVideoSelected: setSelectedVideo,
    uploadTimeout: UPLOAD_TIMEOUT,
    clipId: selectedSong?.id,
  });

  const videoUrl = useMemo(() => {
    // return 'https://cdn1.suno.ai/video_upload_a9ca0db8-8759-4a47-9583-5f10a88c039c.mp4'; // @TODO: just for testing
    if (!selectedVideo) {
      return '';
    }
    return URL.createObjectURL(selectedVideo);
  }, [selectedVideo]);

  // Get video duration when selectedVideo changes
  useEffect(() => {
    if (!videoUrl) {
      setVideoDuration(0);
      return;
    }

    const video = document.createElement('video');
    video.preload = 'metadata';

    const cleanup = () => {
      video.remove();
    };

    video.onloadedmetadata = () => {
      setVideoDuration(Math.round(video.duration * 1000));
      setVideoDimensions([video.videoWidth, video.videoHeight]);
      cleanup();
    };

    video.onerror = () => {
      console.error('Error loading video metadata');
      setVideoDuration(0);
      setVideoDimensions([0, 0]);
      cleanup();
    };

    video.src = videoUrl;

    return cleanup;
  }, [videoUrl]);

  const navigateTo = useCallback((screen: HookFormScreenType) => {
    setCurrentScreen(screen);
  }, []);

  const audioStartMs =
    selectedSong?.audioMetadata?.audio_snippet?.start_timestamp &&
    Math.round(
      selectedSong?.audioMetadata?.audio_snippet?.start_timestamp * 1000
    );
  const mediaEditState = useMediaEditState({
    defaultAudioStart: audioStartMs,
    audioDuration: Math.round((selectedSong?.metadata?.duration ?? 0) * 1000),
    videoDuration: videoDuration,
  });

  const { onCurrentTimeSet, onVideoRangeSet, onAudioRangeSet } = mediaEditState;

  useEffect(() => {
    if (videoDuration) {
      onVideoRangeSet(0, videoDuration);
      onAudioRangeSet(audioStartMs ?? 0);
    }
  }, [videoDuration, audioStartMs, onVideoRangeSet, onAudioRangeSet]);

  useEffect(() => {
    if (selectedSong && audioStartMs != null) {
      onCurrentTimeSet(audioStartMs);
    }
  }, [selectedSong, audioStartMs, onCurrentTimeSet]);

  const resetFormState = useCallback(() => {
    setSelectedSong(null);
    setSelectedVideo(null);
    setVideoDuration(0);
    setVideoDimensions([0, 0]);
    setVideoSound(false);
    setTitle('');
    setCaption('');
    setAllowComments(true);
    setShowLyrics(true);
    setCurrentScreen('selectSongInFlow');

    // Clear upload state
    removeFiles();
  }, [removeFiles]);

  const closeModal = useCallback(() => {
    menus.closeModal(ModalTypes.SELECT_HOOK_SONG);
  }, [menus]);
  const openModal = useCallback(() => {
    menus.openModal(ModalTypes.SELECT_HOOK_SONG);
  }, [menus]);

  // Handle upload errors by reopening the modal to video upload screen
  useEffect(() => {
    if (uploadStatus === 'error') {
      setSelectedVideo(null);
      openModal();
      navigateTo('uploadVideoInFlow');
    }
  }, [uploadStatus, openModal, navigateTo]);

  const createHookMutation = useCreateHookMutation();

  const CancelButton = () => {
    return (
      <Button
        shape={ButtonShape.Pill}
        size={ButtonSize.Small}
        onClick={() => {
          // TODO: mark upload as cancelled on the backend?
        }}
      >
        Cancel
      </Button>
    );
  };

  const ShareButton = ({ hookId }: { hookId: string }) => {
    const ShareButtonComponent = () => {
      const handleShareClick = () => {
        if (!hookId) {
          console.error('Could not find hook ID');
          return;
        }
        const shareLink = generateLinkUrl(hookId, 'hook');
        if (navigator.clipboard && window.isSecureContext) {
          navigator.clipboard
            .writeText(shareLink)
            .then(() => {
              toast({
                title: 'Copied hook link to clipboard',
                status: 'info',
                duration: 2000,
                isClosable: true,
                position: 'top',
              });
            })
            .catch((err) => {
              console.error('Failed to copy: ', err);
            });
        } else {
          alert(
            'Clipboard not supported. Please copy the URL from the address bar.'
          );
        }
      };
      return (
        <Button
          enableHoverState
          variant={ButtonVariant.Aura}
          size={ButtonSize.Small}
          shape={ButtonShape.Pill}
          onClick={handleShareClick}
        >
          Share
        </Button>
      );
    };

    ShareButtonComponent.displayName = 'ShareButtonComponent';
    return ShareButtonComponent;
  };

  // Add this state after the existing state declarations in useHookForm
  const [createdHookId, setCreatedHookId] = useState<string | undefined>(
    undefined
  );
  const uploadingHookToastId = useRef<ReturnType<typeof toast> | undefined>(
    undefined
  );
  const PostToastImageComponent = () => {
    return (
      <div className='relative px-3'>
        <ImageWithFallback
          src={uploadImageUrl}
          className='relative h-16 w-12 rounded-lg object-cover opacity-50'
          alt='Hook Image'
        />
        <div className='absolute inset-0 flex items-center justify-center'>
          {uploadingHookToastId.current ? (
            <SpinnerSVG className='h-6 w-6 text-accent-brand' />
          ) : (
            <CheckIcon className='h-6 w-6' />
          )}
        </div>
      </div>
    );
  };

  // Add hook status polling
  const { data: hookStatus } = useHookById({
    hookId: createdHookId,
    shouldPoll: !!createdHookId,
  });

  const onPostSubmitted = useCallback(() => {
    router.push(`/hooks`);
    uploadingHookToastId.current = toast({
      title: 'Uploading your hook...',
      position: 'top',
      isClosable: false,
      duration: null,
      actionComponent: CancelButton,
      imageComponent: PostToastImageComponent,
    } as ToastV2Props);
    resetFormState();
  }, [router, resetFormState]);

  useEffect(() => {
    if (!createdHookId || !hookStatus) return;

    const terminalStates = [
      HOOK_STATUS.rendered_failed_moderation,
      HOOK_STATUS.rendered_passed_moderation,
      HOOK_STATUS.error,
    ];

    if (terminalStates.includes(hookStatus.status)) {
      if (uploadingHookToastId.current) {
        toast.close(uploadingHookToastId.current);
      }
      // Update toast when hook reaches terminal state
      toast({
        title:
          hookStatus.status === HOOK_STATUS.rendered_passed_moderation
            ? 'Hook posted to feed!'
            : hookStatus.status === HOOK_STATUS.rendered_failed_moderation
              ? 'Hook failed moderation'
              : 'Hook processing failed',
        status:
          hookStatus.status === HOOK_STATUS.rendered_passed_moderation
            ? 'info'
            : 'error',
        position: 'top',
        duration: 3000,
        isClosable: true,
        actionComponent:
          hookStatus.status === HOOK_STATUS.rendered_passed_moderation
            ? ShareButton({ hookId: createdHookId })
            : undefined,
        imageComponent: PostToastImageComponent,
      } as ToastV2Props);

      router.push(`/hook/${createdHookId}`);

      // Clear the hook ID to stop polling and reset form state
      uploadingHookToastId.current = undefined;
      setCreatedHookId(undefined);
    }
  }, [hookStatus, createdHookId, router]);

  const handlePostHook = useCallback(() => {
    let errorMessage = '';

    const requiredFields = [
      { field: 'video', value: selectedVideo },
      { field: 'song', value: selectedSong },
    ];
    for (const field of requiredFields) {
      if (!field.value) {
        errorMessage = `Please select a ${field.field}.`;
        break;
      }
    }
    if (errorMessage) {
      toast({
        title: errorMessage,
        status: 'error',
      });
      return;
    }

    // Log hook submission
    logWebUserEvent({
      actionName: 'HookPostSubmitted',
      context: {
        uploadId: uploadId || undefined,
        clipId: selectedSong?.id || undefined,
      },
    });

    createHookMutation.mutate(
      {
        videoUploadID: uploadId,
        videoFile: selectedVideo,
        clipId: selectedSong?.id || '',
        title: title || '',
        caption: caption,
        allowComments: allowComments,
        showLyrics: showLyrics,
        // Integer milliseconds make life easier in the UI, but they need to be
        // seconds for the backend!
        clipStartTimeMs: mediaEditState.audioStart,
        clipEndTimeMs: mediaEditState.audioEnd,
        clipDurationMs: mediaEditState.audioDuration,
        videoStartTimeMs: mediaEditState.videoStart,
        videoEndTimeMs: mediaEditState.videoEnd,
        clipVolume: videoSound ? 0 : 100,
        videoVolume: videoSound ? 100 : 0,
        customThumbnailS3Id: customThumbnailS3Id,
      },
      {
        onSuccess: (creationResponse) => {
          // Log hook post success
          logWebUserEvent({
            actionName: 'HookPostSucceeded',
            context: {
              uploadId: uploadId || undefined,
              clipId: selectedSong?.id || undefined,
              hookId: creationResponse?.id || undefined,
            },
          });

          setCreatedHookId(creationResponse?.id || undefined);
          onPostSubmitted();
        },
        onError: (_error) => {
          // Log hook post failure
          logWebUserEvent({
            actionName: 'HookPostFailed',
            context: {
              uploadId: uploadId || undefined,
              clipId: selectedSong?.id || undefined,
            },
          });
        },
      }
    );
  }, [
    allowComments,
    title,
    uploadId,
    caption,
    showLyrics,
    createHookMutation,
    onPostSubmitted,
    selectedSong,
    selectedVideo,
    mediaEditState,
    videoSound,
  ]);

  return useMemo(
    () => ({
      currentScreen,
      setCurrentScreen,
      navigateTo,
      selectedSong,
      setSelectedSong,
      selectedVideo,
      updateSelectedVideo,
      uploadStatus,
      videoUrl,
      uploadId,
      videoSound,
      setVideoSound,
      title,
      setTitle,
      caption,
      setCaption,
      allowComments,
      setAllowComments,
      showLyrics,
      setShowLyrics,
      closeModal,
      openModal,
      handlePostHook,
      isCreatingHook: createHookMutation.isPending,
      videoDimensions,
      videoDuration,
      thumbnailTimestamp,
      setThumbnailTimestamp,
      customThumbnailS3Id,
      setCustomThumbnailS3Id,
      mediaEditState,
      createdHookId,
      hookStatus,
      resetFormState,
      uploadProgress,
      audioAlignmentTimestamp,
    }),
    [
      currentScreen,
      navigateTo,
      selectedSong,
      setSelectedSong,
      selectedVideo,
      updateSelectedVideo,
      uploadStatus,
      videoUrl,
      uploadId,
      videoSound,
      setVideoSound,
      title,
      setTitle,
      caption,
      setCaption,
      allowComments,
      setAllowComments,
      showLyrics,
      setShowLyrics,
      closeModal,
      openModal,
      handlePostHook,
      createHookMutation.isPending,
      videoDimensions,
      videoDuration,
      thumbnailTimestamp,
      setThumbnailTimestamp,
      customThumbnailS3Id,
      setCustomThumbnailS3Id,
      mediaEditState,
      createdHookId,
      hookStatus,
      resetFormState,
      uploadProgress,
      audioAlignmentTimestamp,
    ]
  );
};

const HookFormContext = createContext<ReturnType<typeof useHookForm>>(
  undefined as never
);

export const HookFormProvider: React.FC<React.PropsWithChildren> = observer(
  (props) => {
    const hookForm = useHookForm();
    return (
      <HookFormContext.Provider value={hookForm}>
        {props.children}
      </HookFormContext.Provider>
    );
  }
);

export default HookFormContext;
