import styled from '@emotion/styled';
import { useAnimationFrame } from 'framer-motion';
import { useCallback, useRef } from 'react';

import useClickDrag from '@/hooks/useClickDrag';

import { MeterValue } from '../studio/useStudioPlaybackController';
import setCursor from './canvasRenderer/setCursor';

const FaderInteractionArea = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: stretch;
`;

const FaderBackground = styled.div`
  height: 8px;
  width: 100%;
  position: relative;
  background-color: rgba(255, 255, 255, 0.05);
  border-radius: 4px;
`;

const FaderForeground = styled.div`
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: 4px;
  background-color: rgba(255, 255, 255, 0.17);
`;

const FaderDot = styled.div`
  position: absolute;
  width: 32px;
  height: 32px;
  border-radius: 32px;
  top: -12px;
  border: 1px solid rgba(255, 255, 255, 1);
  &:hover,
  &:active {
    background-color: rgba(255, 255, 255, 1);
  }
  z-index: 2;
`;

const getStyleObject = (value: number, anchorValue: number) => {
  return {
    width: Math.abs(value - anchorValue) * 100 + '%',
    left: Math.min(value, anchorValue) * 100 + '%',
  };
};

const MeterWrapper = styled.div`
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  display: grid;
  grid-template-rows: 1fr 1fr;
  gap: 1px;
  overflow: hidden;
  border-radius: inherit;
  cursor: ew-resize;
`;

const MeterBarWrapper = styled.div`
  position: relative;
`;

const MeterBar = styled.div`
  position: absolute;
  top: 0;
  bottom: 0;
  left: -1px;
`;

const PPMMeterBar = styled(MeterBar)`
  background-color: #ccc;
`;

const PPMHoldMeterBar = styled(MeterBar)`
  border-right: 1px solid white;
`;

const VUMeterBar = styled(MeterBar)`
  background-color: #999;
`;

const MAX_DB_METERED = 12;

const curve = (dB: number) => {
  if (dB > -30) {
    return 1 - (dB - MAX_DB_METERED) / -60; // -6db corresponds to -10% of the canvas
  } else if (dB >= -72) {
    const resultAtMinus30 = 1 - (-30 - MAX_DB_METERED) / -60;
    const dbBelowMinus30 = -30 - dB;
    return Math.max(
      1 -
        (-30 - MAX_DB_METERED) / -60 -
        Math.pow(dbBelowMinus30 / 42, 0.75) * resultAtMinus30,
      0
    );
  } else {
    return 0;
  }
};

const MeterRenderer = ({
  getMeterValue,
}: {
  getMeterValue: (channel: number) => MeterValue;
}) => {
  const leftSignalMeterBarPPMRef = useRef<HTMLDivElement>(null);
  const rightSignalMeterBarPPMRef = useRef<HTMLDivElement>(null);

  const leftSignalMeterBarPPMHoldRef = useRef<HTMLDivElement>(null);
  const rightSignalMeterBarPPMHoldRef = useRef<HTMLDivElement>(null);

  const leftSignalMeterBarVURef = useRef<HTMLDivElement>(null);
  const rightSignalMeterBarVURef = useRef<HTMLDivElement>(null);

  useAnimationFrame(
    useCallback(() => {
      const leftSignal = getMeterValue(0);
      const rightSignal = getMeterValue(1);
      if (leftSignalMeterBarPPMRef.current) {
        leftSignalMeterBarPPMRef.current.style.width = `${curve(leftSignal.ppm) * 100}%`;
      }
      if (rightSignalMeterBarPPMRef.current) {
        rightSignalMeterBarPPMRef.current.style.width = `${curve(rightSignal.ppm) * 100}%`;
      }
      if (leftSignalMeterBarPPMHoldRef.current) {
        leftSignalMeterBarPPMHoldRef.current.style.width = `${curve(leftSignal.ppmHold) * 100}%`;
      }
      if (rightSignalMeterBarPPMHoldRef.current) {
        rightSignalMeterBarPPMHoldRef.current.style.width = `${curve(rightSignal.ppmHold) * 100}%`;
      }
      if (leftSignalMeterBarVURef.current) {
        leftSignalMeterBarVURef.current.style.width = `${curve(leftSignal.vu) * 100}%`;
      }
      if (rightSignalMeterBarVURef.current) {
        rightSignalMeterBarVURef.current.style.width = `${curve(rightSignal.vu) * 100}%`;
      }
    }, [getMeterValue])
  );
  return (
    <MeterWrapper>
      <MeterBarWrapper>
        <PPMMeterBar ref={leftSignalMeterBarPPMRef} />
        <VUMeterBar ref={leftSignalMeterBarVURef} />
        <PPMHoldMeterBar ref={leftSignalMeterBarPPMHoldRef} />
      </MeterBarWrapper>
      <MeterBarWrapper>
        <PPMMeterBar ref={rightSignalMeterBarPPMRef} />
        <VUMeterBar ref={rightSignalMeterBarVURef} />
        <PPMHoldMeterBar ref={rightSignalMeterBarPPMHoldRef} />
      </MeterBarWrapper>
    </MeterWrapper>
  );
};

export default function HorizontalFader({
  value,
  anchorValue = 0,
  defaultValue,
  onChange,
  onCommit,
  dotSize = 32,
  getMeterValue,
  disabled,
}: {
  value: number;
  anchorValue?: number;
  defaultValue?: number;
  onChange?: (value: number) => void;
  onCommit?: (value: number) => void;
  dotSize?: number;
  getMeterValue?: (channel: number) => MeterValue;
  disabled?: boolean;
}) {
  const faderBackgroundRef = useRef<HTMLDivElement>(null);
  const faderForegroundRef = useRef<HTMLDivElement>(null);
  const faderDotRef = useRef<HTMLDivElement>(null);

  const clickDragRef = useClickDrag(
    useCallback(
      ({ clientX }) => {
        if (disabled) return;
        const faderBackgroundRect =
          faderBackgroundRef.current?.getBoundingClientRect();
        if (!faderBackgroundRect) return;
        const initialValue = Math.max(
          0,
          Math.min(
            1,
            (clientX - faderBackgroundRect.left) / faderBackgroundRect.width
          )
        );
        const faderBackgroundWidth =
          faderBackgroundRef.current?.clientWidth || 150;
        const unsetCursor = setCursor('ew-resize');
        onChange?.(initialValue);
        return {
          onMouseMove: ({ deltaXFromStart }) => {
            const newValue = Math.min(
              1,
              Math.max(0, initialValue + deltaXFromStart / faderBackgroundWidth)
            );
            onChange?.(newValue);
            if (!faderForegroundRef.current) return;
            const { width, left } = getStyleObject(newValue, anchorValue);
            faderForegroundRef.current!.style.width = width;
            faderForegroundRef.current!.style.left = left;
            if (newValue < anchorValue) {
              faderDotRef.current!.style.right = '';
              faderDotRef.current!.style.left = `-${dotSize / 2}px`;
            } else {
              faderDotRef.current!.style.left = '';
              faderDotRef.current!.style.right = `-${dotSize / 2}px`;
            }
          },
          onMouseUp: ({ deltaXFromStart }) => {
            unsetCursor();
            onCommit?.(
              Math.min(
                1,
                Math.max(
                  0,
                  initialValue + deltaXFromStart / faderBackgroundWidth
                )
              )
            );
          },
        };
      },
      [value, onCommit, onChange, dotSize, disabled]
    )
  );
  return (
    <FaderInteractionArea
      style={{
        opacity: disabled ? 0.5 : 1,
      }}
      ref={clickDragRef}
      onDoubleClick={() => {
        if (disabled) return;
        if (defaultValue !== undefined) {
          onChange?.(defaultValue);
          onCommit?.(defaultValue);
        }
      }}
    >
      <FaderBackground ref={faderBackgroundRef}>
        <FaderForeground
          style={getStyleObject(value, anchorValue)}
          ref={faderForegroundRef}
        >
          <FaderDot
            ref={faderDotRef}
            style={{
              width: dotSize,
              height: dotSize,
              top: -(dotSize - 8) / 2,
              ...(value < anchorValue
                ? { left: -dotSize / 2 }
                : { right: -dotSize / 2 }),
            }}
          />
        </FaderForeground>
        {getMeterValue && <MeterRenderer getMeterValue={getMeterValue} />}
      </FaderBackground>
    </FaderInteractionArea>
  );
}
