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

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;
