import { AudioPlaybackContext } from '@/lib/useAudioPlayback';
import styled from '@emotion/styled';
import { use, useCallback, useContext, useEffect, useMemo, useRef } from 'react';

const Wrapper = styled.div`
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: relative;
  &:before {
    content: '';
    display: block;
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(50% - 1px);
    width: 2px;
    background-color: #ffc350;
    z-index: 2;
  }
`;

const ScrollWrapper = styled.div`
  width: 100%;
  height: 100%;
  overflow-x: scroll;
  overflow-y: hidden;
`;

const ScrollContent = styled.div`
  height: 100%;
  position: relative;
`;

const SlidingWaveform = ({ bands, duration }: { bands: number[][], duration: number }) => {
  const audioPlayback = useContext(AudioPlaybackContext);
  const width = duration * 100;

  const pathDef = useMemo(() => {
    const nodesPerBlock = Math.round(bands[0].length / width);
    const heightRounding = 1;
    let str = '';
    let maxY = 0;
    let lastX = 0;
    for (let i = 0 ; i < bands[0].length; i ++) {
      const x = (i / bands[0].length) * width;
      const y = ((bands.reduce((a, b) => a + b[i], 0) / bands.length) / 4) + 1;
      maxY = Math.ceil(Math.max(maxY, y) / heightRounding) * heightRounding;
      if (i % nodesPerBlock === 0) {
        str += `${i === 0 ? 'M' : 'L'} ${lastX} ${maxY} `;
        str += `L ${x} ${maxY} `;
        lastX = x;
        maxY = 0;
      }
    }
    for (let i = bands[0].length - 1 ; i >= 0; i --) {
      const x = (i / bands[0].length) * width;
      const y = ((bands.reduce((a, b) => a + b[i], 0) / bands.length) / 4) + 1;
      maxY = Math.ceil(Math.max(maxY, y) / heightRounding) * heightRounding;
      if (i % nodesPerBlock === 0) {
        str += `L ${lastX} ${-maxY} `;
        str += `L ${x} ${-maxY} `;
        lastX = x;
        maxY = 0;
      }
    }
    return str;
  }, [bands, width]);

  const scrollWrapperRef = useRef<HTMLDivElement | null>(null);
  const onMouseDown = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
    let lastSeekTime = audioPlayback.getCurrentTime();
    let lastX = e.clientX;

    const seekX = (clientX: number) => {
      if (!e.target) return;
      const boundingRect = (e.target as HTMLElement).getBoundingClientRect();
      const x = clientX;
      const deltaX = x - lastX;
      lastX = x;
      const time = lastSeekTime - ((deltaX / boundingRect.width) * audioPlayback.duration);
      const pathOffset = Math.round(((time / audioPlayback.duration) * width));
      if (scrollWrapperRef.current) {
        scrollWrapperRef.current.scrollLeft = pathOffset;
      }
      lastSeekTime = time;
      audioPlayback.seek(time);
    }

    let wasPlaying = audioPlayback.playing;
    if (audioPlayback.playing) {
      audioPlayback.stop(false);
    }

    seekX(e.clientX);

    const onMouseMove = (e: MouseEvent) => {
      seekX(e.clientX);
    }

    const onMouseUp = (e: MouseEvent) => {
      if (wasPlaying) {
        audioPlayback.play(lastSeekTime);
      }
      window.removeEventListener('mousemove', onMouseMove);
      window.removeEventListener('mouseup', onMouseUp);
    }
    window.addEventListener('mousemove', onMouseMove);
    window.addEventListener('mouseup', onMouseUp);
  }, [audioPlayback]);

  const playbackTimeout = useRef<number | null>(null);
  const handleWheel = useCallback((e: React.WheelEvent<HTMLDivElement>) => {
    if (e.deltaX === 0) return;
    let wasPlaying = audioPlayback.playing;
    if (audioPlayback.playing) {
      audioPlayback.stop();
    }
    if (playbackTimeout.current) {
      wasPlaying = true;
      clearTimeout(playbackTimeout.current);
      playbackTimeout.current = null;
    }
    const time = (e.currentTarget.scrollLeft / width) * audioPlayback.duration;
    audioPlayback.seek(time);
    if (wasPlaying) {
      playbackTimeout.current = window.setTimeout(() => {
        audioPlayback.play(time);
        playbackTimeout.current = null;
      }, 100);
    }
  }, [audioPlayback]);

  const clipRef = useRef<SVGRectElement | null>(null);
  const isAutoScrollRef = useRef(false);
  const handleScroll = useCallback((e: React.UIEvent<HTMLDivElement, UIEvent>) => {
    if (isAutoScrollRef.current) {
      isAutoScrollRef.current = false;
    } else {
      let wasPlaying = audioPlayback.playing;
      if (audioPlayback.playing) {
        audioPlayback.stop();
      }
      if (playbackTimeout.current) {
        wasPlaying = true;
        clearTimeout(playbackTimeout.current);
        playbackTimeout.current = null;
      }
      const time = (e.currentTarget.scrollLeft / width) * audioPlayback.duration;
      audioPlayback.seek(time);
      if (wasPlaying) {
        playbackTimeout.current = window.setTimeout(() => {
          audioPlayback.play(time);
          playbackTimeout.current = null;
        }, 100);
      }
    }
    if (!clipRef.current) return;
    clipRef.current.setAttribute('width', (e.currentTarget.scrollLeft).toString());
  }, [audioPlayback]);

  useEffect(() => {
    const interval = setInterval(() => {
      if (!audioPlayback.duration) return;
      const pathOffset = Math.round(((audioPlayback.getCurrentTime() / audioPlayback.duration) * width));
      if (scrollWrapperRef.current) {
        isAutoScrollRef.current = true;
        scrollWrapperRef.current.scrollLeft = pathOffset;
      }
    }, 20);
    return () => clearInterval(interval);
  }, [audioPlayback]);

  return (
    <Wrapper onMouseDown={onMouseDown}>
      <ScrollWrapper ref={scrollWrapperRef} onWheel={handleWheel} onScroll={handleScroll}>
        <ScrollContent style={{ padding: '0 50%', width: `calc(100% + ${width}px)` }}>
          <svg style={{ width, height: '100%' }} viewBox={`0 -100 ${width} 200`} preserveAspectRatio='none'>
            <clipPath id="waveform-clip">
              <rect x="0" y="-100" width="0" height="200" ref={clipRef} />
            </clipPath>
            <path d={pathDef} fill="#3e3e3e" />
            <g clipPath="url(#waveform-clip)">
              <path d={pathDef} fill="#17706c" />
            </g>
          </svg>
        </ScrollContent>
      </ScrollWrapper>
    </Wrapper>
  )
}

export default SlidingWaveform;
