import styled from '@emotion/styled';
import {
  getWarpBeatsFromSeconds,
  getWarpSecondsFromBeats,
} from '@suno/studiokit/warpUtils';
import { useCallback, useEffect, useMemo, useState } from 'react';

import { useStores } from '@/app/(root)/AppProviders';
import useClip from '@/hooks/useClip';
import {
  useContextSelector,
  useContextSelectorRef,
} from '@/hooks/useContextSelector';
import { CaretLeftIcon, CaretRightIcon } from '@/icons';
import { getClipTitle } from '@/utils/clip';
import { FALLBACK_IMAGE_URL, SMALL_IMAGE } from '@/utils/constants';

import Button, { ButtonShape } from '../button/Button';
import ImageWithFallback from '../image/ImageWithFallback';
import SpinnerSVG from '../svg/SpinnerSVG';
import StudioContext, {
  hasAddStemTask,
  hasInfillTask,
  hasStemConditionTask,
} from './StudioContext';
import { fetchClipArrangementPackage } from './fetchClipArrangementPackage';
import { useClips } from './hooks/useClips';
import { applyInsertionSpec, getAlternateInsertionSpec } from './insertion';
import {
  getDerivedTiming,
  getFocusedArrangementPackage,
  getFocusedStudioClip,
  getSelectionStartBeats,
} from './selectors';
import { AlternateControllerContext } from './useAlternateController';

const Wrapper = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
`;

const AlternateListWrapper = styled.div`
  background-color: var(--color-background-tertiary);
  border-radius: 8px;
  padding: 4px;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
`;

const ClipWrapper = styled.div<{ active: boolean }>`
  display: flex;
  width: 24px;
  height: 32px;
  align-items: center;
  border-radius: 4px;
  overflow: hidden;
  background-color: var(--color-background-primary);
  position: relative;
  ${({ onClick }) => onClick && `cursor: pointer;`}
  &:after {
    content: '';
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border: 1px solid rgba(255, 255, 255, 0);
    border-radius: 4px;
    ${({ active }) => active && `border: 1px solid rgba(255, 255, 255, 1);`}
  }
  &:hover:after {
    ${({ onClick }) => onClick && `border: 1px solid rgba(255, 255, 255, 1);`}
  }
`;

const SpinnerOverlay = styled.div`
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, 0.5);
`;

const IndexOverlay = styled.div`
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'Input Sans', monospace;
  font-size: 14px;
  color: var(--color-foreground-primary);
  font-weight: 600;
  text-shadow: 0 0 1px rgba(0, 0, 0, 1);
`;

export default function FloatingAlternatesList() {
  const focusedStudioClip = useContextSelector(StudioContext, (c) =>
    getFocusedStudioClip(c.nonPreviewState)
  );
  const { clip: focusedClip } = useClip(focusedStudioClip?.clipId ?? undefined);

  if (
    focusedClip &&
    (hasAddStemTask(focusedClip) ||
      hasInfillTask(focusedClip) ||
      hasStemConditionTask(focusedClip))
  ) {
    return <FloatingAlternatesListInner />;
  }

  return null;
}

const EMPTY_ARRAY: [] = [];

const FloatingAlternatesListInner = () => {
  const alternates = useContextSelector(
    AlternateControllerContext,
    (c) => c.list || EMPTY_ARRAY
  );
  const focusedStudioClip = useContextSelector(StudioContext, (c) =>
    getFocusedStudioClip(c.nonPreviewState)
  );
  const setState = useContextSelector(StudioContext, (c) => c.setState);
  const focusedClipArrangementPackage = useContextSelector(StudioContext, (c) =>
    getFocusedArrangementPackage({
      state: c.nonPreviewState,
      clipsById: c.clipsStore.clipById,
    })
  );
  const mode = useContextSelector(StudioContext, (c) => c.mode);
  const nonPreviewStateRef = useContextSelectorRef(
    StudioContext,
    (c) => c.nonPreviewState
  );
  const oneTrackMode = useContextSelector(StudioContext, (c) => c.oneTrackMode);
  const magnetMode = useContextSelector(StudioContext, (c) => c.magnetMode);
  const { clips: clipsStore } = useStores();
  const [loadingAlternate, setLoadingAlternate] = useState<string | null>(null);
  const arrangeAlternate = useCallback(
    async (clipId: string) => {
      const nonPreviewState = nonPreviewStateRef.current;
      if (!focusedStudioClip) {
        console.error('No focused clip!');
        return;
      }

      setLoadingAlternate(clipId);

      try {
        const clipArrangementPackage = await fetchClipArrangementPackage(
          clipsStore,
          clipId
        );

        const selectionStartBeats = getSelectionStartBeats(nonPreviewState);

        const readStartBeats = getWarpBeatsFromSeconds(
          clipArrangementPackage.studioClip.warp,
          getWarpSecondsFromBeats(
            focusedStudioClip.warp,
            focusedStudioClip.readStartBeats
          )
        );

        const alternateInsertionSpec = getAlternateInsertionSpec(
          getDerivedTiming(nonPreviewState),
          clipArrangementPackage,
          focusedClipArrangementPackage,
          nonPreviewState.selection.focusedTrackId,
          oneTrackMode && magnetMode
        );

        const insertionSpec = alternateInsertionSpec || {
          clip: clipArrangementPackage.clip,
          studioClip: {
            ...clipArrangementPackage.studioClip,
            transposition: focusedStudioClip.transposition,
            startBeats: focusedStudioClip.startBeats,
            endBeats: magnetMode
              ? Math.min(
                  focusedStudioClip.endBeats,
                  focusedStudioClip.startBeats +
                    (clipArrangementPackage.studioClip.endBeats -
                      (clipArrangementPackage.studioClip.startBeats +
                        readStartBeats))
                )
              : focusedStudioClip.endBeats,
            readStartBeats,
          },
          replacementStartBeats: magnetMode
            ? selectionStartBeats
            : focusedStudioClip.startBeats,
          replacementEndBeats: magnetMode
            ? selectionStartBeats
            : focusedStudioClip.endBeats,
          trackId: nonPreviewState.selection.focusedTrackId,
        };

        setState(applyInsertionSpec(insertionSpec));
      } catch (e) {
        console.error('Error loading alternate:', e);
        setLoadingAlternate(null);
      }
    },
    [
      focusedStudioClip,
      focusedClipArrangementPackage,
      oneTrackMode,
      magnetMode,
      nonPreviewStateRef,
      mode,
      clipsStore,
      setState,
    ]
  );

  const [prevAlternate, nextAlternate] = useMemo(() => {
    if (!focusedStudioClip) {
      return [
        null,
        alternates.find((alternate) => alternate.status === 'ready') ?? null,
      ];
    }
    const currentIndex = alternates.findIndex((alternate) =>
      alternate.status === 'ready'
        ? alternate.alignedClip.clip.id === focusedStudioClip?.clipId
        : alternate.clipId === focusedStudioClip?.clipId
    );
    const prev = alternates.findLast(
      (alternate, index) => index < currentIndex && alternate.status === 'ready'
    );
    const next = alternates.find(
      (alternate, index) => index > currentIndex && alternate.status === 'ready'
    );
    return [prev, next];
  }, [alternates, focusedStudioClip?.clipId]);

  const alternateClipIds = useMemo(() => {
    return alternates.map((a) =>
      a.status === 'ready' ? a.alignedClip.clip.id : a.clipId
    );
  }, [alternates]);

  const { clips: clipsById } = useClips(alternateClipIds);

  useEffect(() => {
    if (focusedStudioClip?.clipId === loadingAlternate) {
      setLoadingAlternate(null);
    }
  }, [focusedStudioClip?.clipId]);

  if (!focusedStudioClip?.id) {
    return null;
  }

  return (
    <Wrapper>
      <Button
        shape={ButtonShape.Pill}
        icon={<CaretLeftIcon />}
        disabled={prevAlternate?.status !== 'ready'}
        onClick={() => {
          if (prevAlternate?.status === 'ready') {
            arrangeAlternate(prevAlternate.alignedClip.clip.id);
          }
        }}
      />
      <AlternateListWrapper>
        {alternates.map((alternate, index) => {
          const clipId =
            alternate.status === 'ready'
              ? alternate.alignedClip.clip.id
              : alternate.clipId;
          const clip = clipsById[clipId];
          return alternate.status === 'ready' && clip?.audio_url ? (
            <ClipWrapper
              key={clipId}
              active={
                clipId === focusedStudioClip.clipId ||
                loadingAlternate === clipId
              }
              onClick={() => {
                arrangeAlternate(clipId);
              }}
            >
              {loadingAlternate === clipId ? (
                <SpinnerOverlay>
                  <SpinnerSVG className='h-4 w-4' />
                </SpinnerOverlay>
              ) : (
                <IndexOverlay>{index + 1}</IndexOverlay>
              )}
              <ImageWithFallback
                imageSize={SMALL_IMAGE}
                className='h-full w-full object-cover'
                src={clip?.image_url || FALLBACK_IMAGE_URL}
                alt={`${clip ? getClipTitle(clip) : 'Default clip'} artwork`}
              />
            </ClipWrapper>
          ) : (
            <ClipWrapper key={clipId} active={false}>
              <SpinnerOverlay>
                <SpinnerSVG className='h-4 w-4' />
              </SpinnerOverlay>
            </ClipWrapper>
          );
        })}
      </AlternateListWrapper>
      <Button
        shape={ButtonShape.Pill}
        icon={<CaretRightIcon />}
        disabled={nextAlternate?.status !== 'ready'}
        onClick={() => {
          if (nextAlternate?.status === 'ready') {
            arrangeAlternate(nextAlternate.alignedClip.clip.id);
          }
        }}
      />
    </Wrapper>
  );
};
