import type { Meta, StoryObj } from '@storybook/react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { deepCamelKeys } from 'string-ts';

import ALIGNED_LYRICS_RESPONSE_JSON from '@/__fixtures__/lyrics.json';
import Button, {
  ButtonShape,
  ButtonSize,
  ButtonVariant,
} from '@/components/button/Button';
import PlaybackProgress from '@/components/mediaPlayback/PlaybackProgress';
import useMediaPlayback from '@/hooks/useMediaPlayback';
import usePolledValue from '@/hooks/usePolledValue';
import { StatefulPlayPauseIcon } from '@/icons';
import { components } from '@/lib/gen';

import LyricsRenderer, {
  LyricsHighlightMode,
  LyricsRendererProps,
} from './LyricsRenderer';

type AlignedLyricsV2Schema = components['schemas']['AlignedLyricsV2Schema'];

type PagePropsAndCustomArgs = LyricsRendererProps & { audioUrl?: string };

const meta: Meta<PagePropsAndCustomArgs> = {
  title: 'components/LyricsRenderer',
  component: LyricsRenderer,
};

export default meta;
type Story = StoryObj<PagePropsAndCustomArgs>;

const LyricsRendererTest = (props: PagePropsAndCustomArgs) => {
  const { audioUrl, alignedLyrics, ...restProps } = props;

  const lyricsDuration = useMemo(
    () => Math.ceil(alignedLyrics[alignedLyrics.length - 1].endS + 5),
    [alignedLyrics]
  );
  const [isPlaying, setIsPlaying] = useState(true);
  const [isSeeking, setIsSeeking] = useState(false);
  const stateRef = useRef({
    startTime: 0,
    hasAudio: !!audioUrl,
    isPlaying,
    isSeeking,
  });

  const audioPlayback = useMediaPlayback({
    onPlaybackError: () => {
      setIsPlaying(false);
    },
  });
  const audioPlaybackRef = useRef(audioPlayback);
  useEffect(() => {
    audioPlaybackRef.current = audioPlayback;
  }, [audioPlayback]);

  useEffect(() => {
    stateRef.current.isPlaying = isPlaying;
    stateRef.current.isSeeking = isSeeking;
  }, [isPlaying, isSeeking]);

  useEffect(() => {
    stateRef.current.hasAudio = !!audioUrl;
    audioPlaybackRef.current.load(audioUrl || '');
    audioPlaybackRef.current.setLoop(true);
  }, [audioUrl]);

  useEffect(() => {
    if (audioUrl) {
      if (isPlaying) {
        audioPlaybackRef.current.play();
      } else {
        audioPlaybackRef.current.pause();
      }
    }
  }, [audioUrl, isPlaying]);

  const getLyricsProgress = useCallback(() => {
    const time = (performance.now() - stateRef.current.startTime) / 1000;
    if (time >= lyricsDuration) {
      stateRef.current.startTime = performance.now();
      return 0;
    }
    return time;
  }, [lyricsDuration]);

  const getCurrentProgress = audioUrl
    ? audioPlayback.getCurrentTime
    : getLyricsProgress;
  const duration = audioUrl ? audioPlayback.duration : lyricsDuration;

  const [currentTime, updateCurrentProgress] = usePolledValue(
    getCurrentProgress,
    {
      enabled: isPlaying && !isSeeking,
      pollingInterval: 16,
    }
  );

  const handlePlayPauseClick = useCallback(() => {
    setIsPlaying((prevIsPlaying) => !prevIsPlaying);
    updateCurrentProgress();
  }, [updateCurrentProgress]);

  const handleSeekStart = useCallback(() => {
    audioPlaybackRef.current.pause();
    setIsSeeking(true);
  }, []);
  const handleSeekEnd = useCallback(() => {
    if (stateRef.current.isPlaying) {
      audioPlaybackRef.current.play();
    }
    setIsSeeking(false);
  }, []);
  const handleSeekTo = useCallback(
    (time: number | null) => {
      if (time != null) {
        audioPlaybackRef.current.seek(time);
        stateRef.current.startTime = performance.now() - time * 1000;
        updateCurrentProgress();
      }
    },
    [updateCurrentProgress]
  );

  return (
    <div className='fixed inset-0 flex flex-col' data-media-playing={isPlaying}>
      <div className='flex-1 overflow-y-auto'>
        <LyricsRenderer
          alignedLyrics={alignedLyrics}
          currentTime={currentTime}
          {...restProps}
        />
      </div>
      <div className='group flex flex-row items-center justify-center gap-2 p-2'>
        <div>
          <Button
            className='block'
            variant={ButtonVariant.Tertiary}
            shape={ButtonShape.Pill}
            size={ButtonSize.Mini}
            icon={StatefulPlayPauseIcon}
            iconClassName='m-0 w-6 h-6'
            onClick={handlePlayPauseClick}
          />
        </div>
        <div className='flex-1'>
          <PlaybackProgress
            className='w-full'
            currentTime={currentTime}
            duration={duration}
            onSeekStart={handleSeekStart}
            onSeekEnd={handleSeekEnd}
            onSeekMove={handleSeekTo}
            onSeekTo={handleSeekTo}
          />
        </div>
      </div>
    </div>
  );
};

export const Karaoke: Story = {
  render: (args) => {
    return <LyricsRendererTest {...args} />;
  },
  args: {
    className: [
      'absolute inset-0 bg-[#00c] py-2 px-4',
      'flex flex-col items-center justify-center',
      'text-[40px] font-bold',
      '[text-shadow:-2px_-2px_0_#000,2px_-2px_0_#000,-2px_2px_0_#000,2px_2px_0_#000,-2px_0_0_#000,2px_0_0_#000,0_-2px_0_#000,0_2px_0_#000]',
    ].join(' '),
    lineClassName:
      'hidden data-highlight:block data-highlight:text-[#ccc] animate-fade-in',
    wordClassName: 'data-highlight:text-[#ff0] data-highlight:duration-250',
    highlightMode: LyricsHighlightMode.Group,
    groupLines: true,
    groupOffsetStart: -10,
    groupOffsetEnd: 10,
    alignedLyrics: deepCamelKeys(
      ALIGNED_LYRICS_RESPONSE_JSON as AlignedLyricsV2Schema
    ).alignedLyrics!,
    audioUrl: 'https://cdn1.suno.ai/df51e257-d1ce-4013-9dfd-c53379277fb6.webm',
  },
  argTypes: {
    alignedLyrics: { table: { disable: true } },
  },
};

export const Grouped: Story = {
  render: (args) => {
    return <LyricsRendererTest {...args} />;
  },
  args: {
    className: 'py-2 px-4',
    lineClassName: [
      'text-foreground-tertiary opacity-60',
      'data-highlight:opacity-100',
    ].join(' '),
    wordClassName:
      'data-[highlight]:bg-accent-pink-on-primary data-highlight:text-accent-pink-contrast',
    highlightMode: LyricsHighlightMode.Line,
    groupLines: true,
    groupOffsetStart: -1,
    groupOffsetEnd: 1,
    maxGroupDuration: Infinity,
    maxLinesPerGroup: Infinity,
    alignedLyrics: deepCamelKeys(
      ALIGNED_LYRICS_RESPONSE_JSON as AlignedLyricsV2Schema
    ).alignedLyrics!,
    audioUrl: 'https://cdn1.suno.ai/df51e257-d1ce-4013-9dfd-c53379277fb6.webm',
  },
  argTypes: {
    alignedLyrics: { table: { disable: true } },
  },
};

export const Default: Story = {
  render: (args) => {
    return <LyricsRendererTest {...args} />;
  },
  args: {
    className: 'py-2 px-4',
    lineClassName: [
      'text-foreground-tertiary opacity-60',
      'data-highlight:opacity-100',
    ].join(' '),
    alignedLyrics: deepCamelKeys(
      ALIGNED_LYRICS_RESPONSE_JSON as AlignedLyricsV2Schema
    ).alignedLyrics!,
    audioUrl: 'https://cdn1.suno.ai/df51e257-d1ce-4013-9dfd-c53379277fb6.webm',
  },
  argTypes: {
    alignedLyrics: { table: { disable: true } },
  },
};

export const WordByWord: Story = {
  render: (args) => {
    return <LyricsRendererTest {...args} />;
  },
  args: {
    className: [
      'absolute inset-0 bg-[#000] py-2 px-4',
      'flex flex-col items-center justify-center',
      'text-[40px] font-bold',
      '[text-shadow:-2px_-2px_0_#000,2px_-2px_0_#000,-2px_2px_0_#000,2px_2px_0_#000,-2px_0_0_#000,2px_0_0_#000,0_-2px_0_#000,0_2px_0_#000]',
    ].join(' '),
    wordClassName: 'hidden data-highlight:block data-highlight:text-[#ff0]',
    highlightMode: LyricsHighlightMode.Word,
    alignedLyrics: deepCamelKeys(
      ALIGNED_LYRICS_RESPONSE_JSON as AlignedLyricsV2Schema
    ).alignedLyrics!,
    audioUrl: 'https://cdn1.suno.ai/df51e257-d1ce-4013-9dfd-c53379277fb6.webm',
  },
  argTypes: {
    alignedLyrics: { table: { disable: true } },
  },
};
