import { useCallback, useEffect, useMemo, useRef, useState } from 'react';

import useDismount from '@/hooks/useDismount';
import { Clip } from '@/state/clipStore';

import clearMuteAndSolo from './actions/clearMuteAndSolo';
import { combineActions } from './actions/combineActions';
import toggleTrackSolo from './actions/toggleTrackSolo';
import createPreviewStudioClipSync from './createPreviewStudioClipSync';
import describeClip from './describeClipTiming';
import { applyInsertionSpec } from './insertion';
import { getSelectionEndBeats, getSelectionStartBeats } from './selectors';
import { StudioProjectState } from './types';
import useFfmpegBufferCache from './useFfmpegBufferCache';
import useStudioPlaybackController from './useStudioPlaybackController';

export default function usePreviewController(
  baseState: StudioProjectState,
  previewBeforeInsertMode: boolean
) {
  const playbackControllerRef = useRef<ReturnType<
    typeof useStudioPlaybackController
  > | null>(null);

  const receivePlaybackController = useCallback(
    (playbackController: ReturnType<typeof useStudioPlaybackController>) => {
      playbackControllerRef.current = playbackController;
    },
    []
  );

  const [previewFunctionPackage, setPreviewFunctionPackage] = useState<{
    previewState: (state: StudioProjectState) => StudioProjectState;
    offTimeline: boolean;
    id: string;
    startBeats?: number;
    endBeats?: number;
    shouldAutoPlay: boolean;
  } | null>(null);

  const previewPackage = useMemo(() => {
    if (previewFunctionPackage) {
      const state = previewFunctionPackage.previewState(baseState);
      return {
        state: state,
        offTimeline: previewFunctionPackage.offTimeline,
        startBeats:
          previewFunctionPackage.startBeats ?? getSelectionStartBeats(state),
        endBeats:
          previewFunctionPackage.endBeats ?? getSelectionEndBeats(state),
        id: previewFunctionPackage.id,
        shouldAutoPlay: previewFunctionPackage.shouldAutoPlay,
      };
    }
    return null;
  }, [previewFunctionPackage, baseState]);

  const startPreviewing = useCallback(
    (
      id: string,
      inputPreviewState: (state: StudioProjectState) => StudioProjectState,
      offTimeline: boolean = false,
      startBeats?: number,
      endBeats?: number,
      shouldAutoPlay: boolean = true
    ) => {
      if (previewBeforeInsertMode) {
        setPreviewFunctionPackage({
          previewState: () => inputPreviewState(baseState),
          id,
          offTimeline,
          startBeats,
          endBeats,
          shouldAutoPlay,
        });
      } else {
        setPreviewFunctionPackage({
          previewState: inputPreviewState,
          id,
          offTimeline,
          startBeats,
          endBeats,
          shouldAutoPlay,
        });
      }
    },
    // hax to reduce depency re-renders
    previewBeforeInsertMode
      ? [previewBeforeInsertMode, baseState]
      : [previewBeforeInsertMode, null]
  );

  const lastPreviewPackageIdRef = useRef<string | null>(null);
  useEffect(() => {
    if (
      previewPackage &&
      previewPackage.id !== lastPreviewPackageIdRef.current
    ) {
      lastPreviewPackageIdRef.current = previewPackage.id;
      if (previewPackage.shouldAutoPlay) {
        const playbackController = playbackControllerRef.current;
        if (!playbackController) {
          console.error('no playback controller');
          return;
        }

        playbackController.stop();

        setTimeout(() => {
          playbackController.seek(
            previewPackage.offTimeline
              ? previewPackage.startBeats
              : previewPackage.startBeats - 2
          );
          if (previewPackage.offTimeline) {
            playbackController.stopAt(previewPackage.endBeats);
          }
          playbackController.play();
        }, 0);
      }
    } else if (!previewPackage && lastPreviewPackageIdRef.current) {
      lastPreviewPackageIdRef.current = null;
      playbackControllerRef.current?.stop();
    }
  }, [previewPackage]);

  const { waitUntilPlayable } = useFfmpegBufferCache();

  const lastRequestedClipIdRef = useRef<string | null>(null);

  const previewOffTimeline = useCallback(
    async (clip: Clip, includeContextWindow: boolean = false) => {
      lastRequestedClipIdRef.current = clip.id;
      const contentStartSeconds = describeClip(clip).contentStartSeconds;
      await waitUntilPlayable(clip, Math.max(1, contentStartSeconds)); // wait for at least 1 second to avoid glitches
      if (lastRequestedClipIdRef.current !== clip.id) return;
      const studioClip = createPreviewStudioClipSync(
        clip,
        includeContextWindow
      );

      startPreviewing(
        clip.id,
        combineActions(
          clearMuteAndSolo(),
          applyInsertionSpec(
            {
              clip,
              studioClip: studioClip,
              replacementStartBeats: studioClip.startBeats,
              replacementEndBeats: studioClip.endBeats,
              trackId: null,
            },
            true
          ),
          (state) => {
            const lastTrackId = state.tracks[state.tracks.length - 1].id;
            return toggleTrackSolo(lastTrackId, false)(state);
          },
          (state) => {
            return {
              ...state,
              loop: {
                ...state.loop,
                enabled: false,
              },
              metronome: {
                ...state.metronome,
                enabled: false,
              },
              timing: {
                type: 'manual',
                bps: 1,
                bpsAutomation: [],
              },
            };
          }
        ),
        true,
        studioClip.startBeats,
        studioClip.endBeats
      );
    },
    [waitUntilPlayable]
  );

  const stopPreviewing = useCallback(() => {
    setPreviewFunctionPackage(null);
  }, []);

  useDismount(() => {
    stopPreviewing();
  }, 250);

  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        stopPreviewing();
      }
    };
    window.addEventListener('keydown', handleKeyDown);
    return () => {
      window.removeEventListener('keydown', handleKeyDown);
    };
  }, [stopPreviewing]);

  const previewingOnTimeline = Boolean(
    previewPackage && !previewPackage.offTimeline
  );
  const previewingOffTimeline = Boolean(
    previewPackage && previewPackage.offTimeline
  );

  return useMemo(
    () => ({
      previewingOnTimeline,
      previewingOffTimeline,
      previewPackage,
      startPreviewing,
      stopPreviewing,
      previewOffTimeline,
      receivePlaybackController,
    }),
    [
      previewingOnTimeline,
      previewingOffTimeline,
      previewPackage,
      startPreviewing,
      stopPreviewing,
      previewOffTimeline,
      receivePlaybackController,
    ]
  );
}
