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

import { toast } from '@/components/toast/Toast';
import { useFetchClip } from '@/hooks/useClip';
import { useContextSelector } from '@/hooks/useContextSelector';
import { AudioFileIcon, CaretDownIcon, SlidersIcon } from '@/icons';
import { useApiClient } from '@/lib/apiClient';
import { Clip } from '@/state/clipStore';
import { downloadMultitrackV2 } from '@/utils/download';

import Button, { ButtonShape } from '../button/Button';
import {
  ContextMenuItem,
  ContextMenuTrigger,
} from '../contextMenu/ContextMenu';
import downloadAsZip from '../edit2025/downloadAsZip';
import { getDownbeatsFromTiming } from '../edit2025/getTimingFromDownbeats';
import { getPlaintextLyrics } from '../edit2025/lyrics/getPlaintextLyrics';
import SpinnerSVG from '../svg/SpinnerSVG';
import { Tooltip } from '../tooltip/Tooltip';
import StudioContext from './StudioContext';
import { StudioProjectManagementContext } from './StudioProjectManagementContext';
import clearMuteAndSolo from './actions/clearMuteAndSolo';
import removeNonUploadedClips from './actions/removeNonUploadedClips';
import getFullVolumeSingleTrackState from './getFullVolumeSingleTrackState';
import getStateAlignedLyrics from './getStateAlignedLyrics';
import resolveHeardState from './resolveHeardState';
import {
  getDerivedTiming,
  getEarliestClipBeats,
  getEffectivelyMutedTracks,
  getLatestClipBeats,
  getSelectionEndBeats,
  getSelectionStartBeats,
  getSongEndBeats,
  getSongStartBeats,
} from './selectors';
import { useLogStudioWebUserEvent } from './useLogStudioWebUserEvent';

const USE_MULTITRACK_V2 = true;

export default function StudioExportMenu({
  setRenderedClip,
}: {
  setRenderedClip: Dispatch<SetStateAction<Clip | null>>;
}) {
  const apiClient = useApiClient();
  const stateRef = useContextSelector(
    StudioContext,
    (context) => context.stateRef
  );
  const projectId = useContextSelector(
    StudioContext,
    (context) => context.projectId
  );
  const studioProjectId = useContextSelector(
    StudioContext,
    (context) => context.studioProjectId
  );
  const projectTitle = useContextSelector(
    StudioProjectManagementContext,
    (context) => context.loadedProject?.title || 'Untitled Project'
  );
  const showBeats = useContextSelector(
    StudioContext,
    (context) => context.timelineController.showBeats
  );
  const earliestClipBeats = useContextSelector(StudioContext, (context) =>
    getEarliestClipBeats(context.state)
  );
  const latestClipBeats = useContextSelector(StudioContext, (context) =>
    getLatestClipBeats(context.state)
  );
  const alignedLyricsByClipId = useContextSelector(
    StudioContext,
    (context) => context.alignedLyricsByClipId
  );

  const projectIsEmpty = useContextSelector(
    StudioContext,
    (context) =>
      context.state.tracks.length === 0 ||
      context.state.tracks.every(
        (t) =>
          t.clips.length === 0 && t.takeLanes.every((t) => t.clips.length === 0)
      )
  );
  const projectWasEmpty = useRef(projectIsEmpty);
  useEffect(() => {
    if (projectWasEmpty.current && !projectIsEmpty) {
      showBeats(earliestClipBeats, latestClipBeats, true);
    }
    projectWasEmpty.current = projectIsEmpty;
  }, [projectIsEmpty, earliestClipBeats, latestClipBeats, showBeats]);

  const hasZeroLengthSelection = useContextSelector(
    StudioContext,
    (context) =>
      getSelectionStartBeats(context.state) ===
      getSelectionEndBeats(context.state)
  );

  const fetchClip = useFetchClip();

  const [rendering, setRendering] = useState(false);

  const handleRender = useCallback(
    async (forceFullSong: boolean = false) => {
      const state = stateRef.current;
      if (
        state.tracks.length === 0 ||
        state.tracks.every((t) => t.clips.length === 0)
      ) {
        toast({
          title: 'No tracks or clips',
          description: 'Please add a track and clip to export.',
          status: 'error',
          duration: 5000,
          isClosable: true,
        });
        return;
      }
      setRendering(true);
      try {
        const selectionStartBeats = getSelectionStartBeats(state);
        const selectionEndBeats = getSelectionEndBeats(state);
        const mutedTracks = getEffectivelyMutedTracks(state);
        const stateWithUnmutedTracksOnly = {
          ...state,
          tracks: state.tracks.filter((t) => !mutedTracks[t.id]),
        };
        const startBeats =
          selectionStartBeats === selectionEndBeats || forceFullSong
            ? getSongStartBeats(stateWithUnmutedTracksOnly)
            : selectionStartBeats;
        const endBeats =
          selectionStartBeats === selectionEndBeats || forceFullSong
            ? getSongEndBeats(stateWithUnmutedTracksOnly)
            : selectionEndBeats;
        const result = await apiClient.POST('/api/studio/render-state', {
          body: {
            // note: do not pass from_studio_project_id here. that property is only intended for clips created from within the Studio UI, not for rendered outputs.
            title: `${projectTitle || 'Untitled Studio Project'}`,
            lyrics: getPlaintextLyrics(
              getStateAlignedLyrics(
                stateWithUnmutedTracksOnly,
                alignedLyricsByClipId,
                {
                  trackIds: stateWithUnmutedTracksOnly.tracks.map((t) => t.id),
                  startBeats,
                  endBeats,
                }
              )
            ),
            tags: '',
            negative_tags: '',
            style_summary: '',
            caption: '',
            state: {
              ...resolveHeardState(
                removeNonUploadedClips(stateWithUnmutedTracksOnly)
              ),
              timing: {
                type: 'manual',
                ...getDerivedTiming(state),
              },
            },
            start_beats: startBeats,
            end_beats: endBeats,
            project_id: projectId,
            web_client_pathname: window.location.pathname,
            downbeats: getDownbeatsFromTiming(
              getDerivedTiming(state),
              startBeats,
              endBeats
            ),
          },
        });
        if (!result.data) {
          console.error(result);
          toast({
            title: 'Error',
            description: 'Something went wrong. Please try again.',
            status: 'error',
            duration: 5000,
            isClosable: true,
          });
        } else if ((result.data as any)?.moderation_error_message) {
          toast({
            title: 'Moderation error',
            description: (result.data as any).moderation_error_message,
            status: 'error',
            duration: 5000,
            isClosable: true,
          });
        } else if ((result.data as any)?.id) {
          setRenderedClip(result.data as Clip);
        }
      } finally {
        setRendering(false);
      }
    },
    [stateRef, projectTitle, projectId]
  );

  const handleRenderMultitrack = useCallback(async () => {
    const state = stateRef.current;
    if (
      state.tracks.length === 0 ||
      state.tracks.every((t) => t.clips.length === 0)
    ) {
      toast({
        title: 'No tracks or clips',
        description: 'Please add a track and clip to export.',
        status: 'error',
        duration: 5000,
        isClosable: true,
      });
      return;
    }
    setRendering(true);
    try {
      const nonEmptyTracks = state.tracks.filter((t) => t.clips.length > 0);
      const clipsToFetch: string[] = [];

      toast({
        title: `Exporting ${nonEmptyTracks.length} track${
          nonEmptyTracks.length === 1 ? '' : 's'
        }.`,
        description: 'This may take several minutes.',
        status: 'info',
        duration: 5000,
        isClosable: true,
      });

      for (let i = 0; i < nonEmptyTracks.length; i++) {
        const track = nonEmptyTracks[i];
        const trackName = track.name.trim() || 'Track ' + (i + 1);
        const timing = getDerivedTiming(state);
        const downbeats = getDownbeatsFromTiming(
          timing,
          getSongStartBeats(state),
          getSongEndBeats(state)
        );
        const singleTrackState = getFullVolumeSingleTrackState(
          timing,
          track.clips,
          track.id
        );
        const result = await apiClient.POST('/api/studio/render-state', {
          body: {
            title: `${projectTitle || 'Untitled Project'} (${trackName})`,
            from_studio_project_id: studioProjectId,
            web_client_pathname: window.location.pathname,
            lyrics: getPlaintextLyrics(
              getStateAlignedLyrics(state, alignedLyricsByClipId, {
                trackIds: [track.id],
                startBeats: getSongStartBeats(state),
                endBeats: getSongEndBeats(state),
              })
            ),
            tags: '',
            negative_tags: '',
            style_summary: '',
            caption: '',
            state: removeNonUploadedClips(singleTrackState),
            start_beats: getSongStartBeats(state),
            end_beats: getSongEndBeats(state),
            project_id: projectId,
            downbeats: downbeats,
          },
        });
        if (result.data && !(result.data as any).moderation_error_message) {
          clipsToFetch.push((result.data as any).id);
        } else if (
          result.data &&
          (result.data as any).moderation_error_message
        ) {
          toast({
            title: 'Moderation error',
            description: (result.data as any).moderation_error_message,
            status: 'error',
            duration: 5000,
            isClosable: true,
          });
        } else {
          console.error(result);
          toast({
            title: 'Error',
            description: `Track "${trackName}" failed to render.`,
            status: 'warning',
            duration: 5000,
            isClosable: true,
          });
        }
      }

      const clips = await Promise.all(
        clipsToFetch.map((clipId) => fetchClip(clipId))
      );

      toast({
        title: `Downloading ${clips.length} track${
          clips.length === 1 ? '' : 's'
        }.`,
        description: 'This may take several minutes.',
        status: 'info',
        duration: 5000,
        isClosable: true,
      });

      await downloadAsZip(
        apiClient,
        clips,
        `${projectTitle || 'Untitled Project'} (Multitrack)`,
        'wav'
      );

      toast({
        title: 'Download complete!',
        status: 'info',
        duration: 5000,
        isClosable: true,
      });
    } catch (e) {
      console.error(e);
      toast({
        title: 'Error',
        description: 'Something went wrong. Please try again.',
        status: 'error',
        duration: 5000,
        isClosable: true,
      });
    } finally {
      setRendering(false);
    }
  }, [
    stateRef,
    projectId,
    studioProjectId,
    apiClient,
    alignedLyricsByClipId,
    projectTitle,
    fetchClip,
  ]);

  const handleRenderMultitrackV2 = useCallback(async () => {
    const state = stateRef.current;
    if (
      state.tracks.length === 0 ||
      state.tracks.every((t) => t.clips.length === 0)
    ) {
      toast({
        title: 'No tracks or clips',
        description: 'Please add a track and clip to export.',
        status: 'error',
        duration: 5000,
        isClosable: true,
      });
      return;
    }
    setRendering(true);
    try {
      const startBeats = getSongStartBeats(state);
      const endBeats = getSongEndBeats(state);

      // Unmute and unsolo all tracks
      const stateWithAllTracksAudible = clearMuteAndSolo()(state);

      const renderStateBody = {
        title: `${projectTitle || 'Untitled Studio Project'}`,
        from_studio_project_id: studioProjectId,
        web_client_pathname: window.location.pathname,
        lyrics: getPlaintextLyrics(
          getStateAlignedLyrics(
            stateWithAllTracksAudible,
            alignedLyricsByClipId,
            {
              trackIds: stateWithAllTracksAudible.tracks.map((t) => t.id),
              startBeats,
              endBeats,
            }
          )
        ),
        tags: '',
        negative_tags: '',
        style_summary: '',
        caption: '',
        state: {
          ...resolveHeardState(
            removeNonUploadedClips(stateWithAllTracksAudible)
          ),
          timing: {
            type: 'manual',
            ...getDerivedTiming(state),
          },
        },
        start_beats: startBeats,
        end_beats: endBeats,
        project_id: projectId,
        downbeats: getDownbeatsFromTiming(
          getDerivedTiming(state),
          startBeats,
          endBeats
        ),
        format: 'wav' as const,
      };

      await downloadMultitrackV2(apiClient, renderStateBody);

      toast({
        title: 'Multitrack download started!',
        duration: 5000,
        isClosable: true,
      });
    } catch (e) {
      console.error(e);
      toast({
        title: 'Error',
        description: 'Something went wrong. Please try again.',
        status: 'error',
        duration: 5000,
        isClosable: true,
      });
    } finally {
      setRendering(false);
    }
  }, [
    stateRef,
    projectTitle,
    projectId,
    apiClient,
    alignedLyricsByClipId,
    studioProjectId,
  ]);

  const logStudioWebUserEvent = useLogStudioWebUserEvent();

  return (
    <ContextMenuTrigger
      ButtonComponent={(props) => (
        <Button
          {...props}
          disabled={rendering || projectIsEmpty}
          shape={ButtonShape.Pill}
          icon={
            rendering ? (
              <SpinnerSVG className='h-4 w-4' />
            ) : (
              <CaretDownIcon className='h-4 w-4' />
            )
          }
        >
          {rendering ? 'Exporting...' : 'Export'}
        </Button>
      )}
      ContentsComponent={() => (
        <>
          <ContextMenuItem
            icon={AudioFileIcon}
            onClick={() => {
              handleRender(true);
              logStudioWebUserEvent({
                actionName: 'StudioExport',
                context: {
                  exportMode: 'full_song',
                  exportFormat: 'wav',
                },
              });
            }}
          >
            Full Song
          </ContextMenuItem>
          <Tooltip
            label={
              hasZeroLengthSelection
                ? 'Selection must be longer than zero beats '
                : ''
            }
          >
            <ContextMenuItem
              disabled={hasZeroLengthSelection}
              icon={AudioFileIcon}
              onClick={() => {
                handleRender();
                logStudioWebUserEvent({
                  actionName: 'StudioExport',
                  context: {
                    exportMode: 'selected_time_range',
                    exportFormat: 'wav',
                  },
                });
              }}
            >
              Selected Time Range
            </ContextMenuItem>
          </Tooltip>
          <ContextMenuItem
            icon={SlidersIcon}
            onClick={() => {
              if (USE_MULTITRACK_V2) {
                handleRenderMultitrackV2();
              } else {
                handleRenderMultitrack();
              }
              logStudioWebUserEvent({
                actionName: 'StudioExport',
                context: {
                  exportMode: 'multitrack',
                  exportFormat: 'wav',
                },
              });
            }}
          >
            Multitrack
          </ContextMenuItem>
        </>
      )}
    />
  );
}
