import styled from '@emotion/styled';
import {
  dbToMultiplier,
  multiplierToDb,
} from '@suno/studiokit/audioEngineeringUtils';
import { useAnimationFrame } from 'framer-motion';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';

import useClickDrag from '@/hooks/useClickDrag';

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

const FaderInteractionArea = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: stretch;
  cursor: ew-resize;
  &:not(:hover, .dragging) .fader-knob {
    background-color: transparent;
    border-color: transparent;
    box-shadow: none;
    .fader-value-display {
      opacity: 0;
    }
    &:after {
      content: '';
      display: block;
      position: absolute;
      top: -2px;
      bottom: -2px;
      z-index: -1;
      left: -1.5px;
      right: -1.5px;
      border-radius: 14px;
      background-color: var(--color-foreground-inactive);
    }
  }
  .hover-emphasize {
    opacity: 0.25;
    transition: opacity 0.15s ease;
  }
  &:hover .hover-emphasize {
    opacity: 1;
  }
`;

const FaderBackground = styled.div<{ hasMeter?: boolean }>`
  height: ${({ hasMeter }) => (hasMeter ? 16 : 7)}px;
  width: 100%;
  position: relative;
`;

const FaderForeground = styled.div`
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 2;
`;

const FaderKnob = styled.div`
  position: absolute;
  z-index: 3;
  transition: opacity 0.1s ease-in-out;

  width: 11px;
  height: 24px;
  border-radius: 14px;
  border: 4px solid white;
  background-color: var(--color-background-tertiary);
  box-shadow: 0 0 2px var(--color-background-primary);
`;

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

const TooltipTouchTarget = styled.div`
  position: absolute;
  top: -4px;
  bottom: -4px;
  left: -4px;
  right: -4px;
`;

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;
`;

const MeterBarWrapper = styled.div`
  position: relative;
  border-radius: 10px;
  overflow: hidden;
  background-color: var(--color-background-glass-thin);
  container-type: inline-size;
`;

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

const BackgroundSegmentWrapper = styled.div`
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  display: grid;
  gap: 5px;
  grid-template-columns: 1fr auto 1fr;
`;

const BackgroundSegmentDisplay = styled.div`
  position: relative;
  background-color: var(--color-background-glass-thin);
  border-radius: 10px;
  overflow: hidden;
`;

const BackgroundSegmentDisplayInner = styled.div`
  background-color: rgb(85, 85, 85);
  position: absolute;
  height: 100%;
`;

const AnchorDisplay = styled.div`
  background-color: var(--color-background-glass-thick);
  border-radius: 10px;
  width: 3px;
  margin-top: -4px;
  margin-bottom: -4px;
`;

const PPMMeterBar = styled(MeterBar)`
  background-color: rgb(75, 75, 75);
  border-radius: inherit;
`;

const PPMHoldMeterBar = styled(MeterBar)`
  &:after {
    content: '';
    display: block;
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    width: 3px;
    border-radius: 8px;
    background-color: rgb(75, 75, 75);
  }
`;

const VUMeterBar = styled(MeterBar)<{ color: string; clipped: boolean }>`
  ${({ clipped, color }) =>
    clipped
      ? 'background-color: var(--color-accent-orange);'
      : `background-image: linear-gradient(
    to right,
    white 0px,
    ${color} 100cqw
  );`}
  border-radius: inherit;
`;

const FaderValueDisplay = styled.div`
  background-color: var(--color-dumbo-300);
  color: var(--color-dumbo-900);
  padding: 2px 4px;
  border-radius: 4px;
  margin-top: 4px;
  pointer-events: none;
  z-index: 2;
  white-space: nowrap;
  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.75);
  transition: opacity 0.2s ease-in-out;
  font-family: 'Input Sans', monospace;
  font-size: 10px;
`;

const MeterAnchorDisplay = styled.div`
  position: absolute;
  top: -6px;
  bottom: -6px;
  width: 1px;
  z-index: 2;
  &:before,
  &:after {
    border-radius: 1px;
    content: '';
    display: block;
    position: absolute;
    left: -1.5px;
    width: 2px;
    height: 5px;
    background-color: var(--color-foreground-inactive);
  }
  &:before {
    top: 0;
  }
  &:after {
    bottom: 0;
  }
`;

const MAX_DB_METERED = 12;

export const dbToZeroOne = (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 resultAtMinus30 = dbToZeroOne(-30);
export const zeroOneToDb = (value: number) => {
  if (value >= resultAtMinus30) {
    // Linear region: dB > -30
    // dbToZeroOne(dB) = 1 - (dB - MAX_DB_METERED) / -60
    // Let v = dbToZeroOne(dB) = 1 - (dB - 12) / -60
    // v - 1 = - (dB - 12) / -60 = (dB - 12) / 60
    // 60(v - 1) = dB - 12
    // dB = 60(v - 1) + 12
    return 60 * (value - 1) + MAX_DB_METERED;
  } else if (value > 0) {
    // Non-linear region: -72 <= dB <= -30
    // From dbToZeroOne:
    // resultAtMinus30 - Math.pow(dbBelowMinus30 / 42, 0.75) * resultAtMinus30
    // where dbBelowMinus30 = -30 - dB
    // Let v = resultAtMinus30 - Math.pow((-30 - dB)/42, 0.75) * resultAtMinus30
    // Math.pow((-30 - dB)/42, 0.75) = (resultAtMinus30 - v) / resultAtMinus30
    // (-30 - dB)/42 = Math.pow((resultAtMinus30 - v) / resultAtMinus30, 1/0.75)
    // -30 - dB = 42 * Math.pow((resultAtMinus30 - v) / resultAtMinus30, 1/0.75)
    // dB = -30 - 42 * Math.pow((resultAtMinus30 - v) / resultAtMinus30, 1/0.75)
    const x = Math.pow((resultAtMinus30 - value) / resultAtMinus30, 1 / 0.75);
    return -30 - 42 * x;
  } else {
    // dB <= -72
    return -72;
  }
};

export const multiplierToScaledZeroOne = (multiplier: number) => {
  return dbToZeroOne(multiplierToDb(multiplier));
};

export const scaledZeroOneToMultiplier = (value: number) => {
  return dbToMultiplier(zeroOneToDb(value));
};

const MeterRenderer = ({
  getMeterValue,
  color,
  warnOnClip,
}: {
  getMeterValue: (channel: number) => MeterValue;
  color: string;
  warnOnClip?: boolean;
}) => {
  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);

  const [clipped, setClipped] = useState(false);

  useAnimationFrame(
    useCallback(() => {
      const leftSignal = getMeterValue(0);
      const rightSignal = getMeterValue(1);
      if (leftSignalMeterBarPPMRef.current) {
        leftSignalMeterBarPPMRef.current.style.width = `${dbToZeroOne(leftSignal.ppm) * 100}%`;
      }
      if (rightSignalMeterBarPPMRef.current) {
        rightSignalMeterBarPPMRef.current.style.width = `${dbToZeroOne(rightSignal.ppm) * 100}%`;
      }
      if (leftSignalMeterBarPPMHoldRef.current) {
        leftSignalMeterBarPPMHoldRef.current.style.width = `${dbToZeroOne(leftSignal.ppmHold) * 100}%`;
      }
      if (rightSignalMeterBarPPMHoldRef.current) {
        rightSignalMeterBarPPMHoldRef.current.style.width = `${dbToZeroOne(rightSignal.ppmHold) * 100}%`;
      }
      if (leftSignalMeterBarVURef.current) {
        leftSignalMeterBarVURef.current.style.width = `${dbToZeroOne(leftSignal.vu) * 100}%`;
      }
      if (rightSignalMeterBarVURef.current) {
        rightSignalMeterBarVURef.current.style.width = `${dbToZeroOne(rightSignal.vu) * 100}%`;
      }
      if (warnOnClip) {
        setClipped(leftSignal.ppmHold > 0 || rightSignal.ppmHold > 0);
      }
    }, [getMeterValue, warnOnClip])
  );

  return (
    <MeterWrapper>
      <MeterBarWrapper>
        <PPMMeterBar ref={leftSignalMeterBarPPMRef} />
        <VUMeterBar
          ref={leftSignalMeterBarVURef}
          color={color}
          clipped={!!warnOnClip && clipped}
        />
        <PPMHoldMeterBar ref={leftSignalMeterBarPPMHoldRef} />
      </MeterBarWrapper>
      <MeterBarWrapper>
        <PPMMeterBar ref={rightSignalMeterBarPPMRef} />
        <VUMeterBar
          ref={rightSignalMeterBarVURef}
          color={color}
          clipped={!!warnOnClip && clipped}
        />
        <PPMHoldMeterBar ref={rightSignalMeterBarPPMHoldRef} />
      </MeterBarWrapper>
    </MeterWrapper>
  );
};

export default function StudioHorizontalFader({
  value,
  anchorValue = 0,
  defaultValue,
  onChange,
  onCommit,
  getMeterValue,
  disabled,
  formatValue,
  color,
  tooltipPlacement = 'bottom',
  warnOnClip = false,
}: {
  value: number;
  anchorValue?: number;
  defaultValue?: number;
  onChange?: (value: number, isFirst: boolean) => void;
  onCommit?: (value: number) => void;
  getMeterValue?: (channel: number) => MeterValue;
  disabled?: boolean;
  formatValue?: (value: number) => string;
  color: string;
  tooltipPlacement?: 'bottom' | 'top';
  warnOnClip?: boolean;
}) {
  const faderValueDisplayRef = useRef<HTMLDivElement>(null);

  const faderBackgroundRef = useRef<HTMLDivElement>(null);
  const faderForegroundRef = useRef<HTMLDivElement>(null);
  const faderKnobRef = useRef<HTMLDivElement>(null);
  const preAnchorRef = useRef<HTMLDivElement>(null);
  const postAnchorRef = useRef<HTMLDivElement>(null);

  const [isDragging, setIsDragging] = useState(false);

  const updateDisplay = useCallback(
    (newValue: number) => {
      if (!faderForegroundRef.current) return;
      const { width, left } = getStyleObject(newValue, anchorValue);
      faderForegroundRef.current!.style.width = width;
      faderForegroundRef.current!.style.left = left;
      if (newValue < anchorValue) {
        faderKnobRef.current!.style.right = '';
        faderKnobRef.current!.style.left = `-5px`;
        if (preAnchorRef.current) {
          preAnchorRef.current.style.width = `${2 * (anchorValue - newValue) * 100}%`;
        }
        if (postAnchorRef.current) {
          postAnchorRef.current.style.width = '0';
        }
      } else {
        faderKnobRef.current!.style.left = '';
        faderKnobRef.current!.style.right = `-5px`;
        if (preAnchorRef.current) {
          preAnchorRef.current.style.width = '0';
        }
        if (postAnchorRef.current) {
          postAnchorRef.current.style.width = `${2 * (newValue - anchorValue) * 100}%`;
        }
      }
      if (faderValueDisplayRef.current && formatValue) {
        faderValueDisplayRef.current.textContent = formatValue(newValue) || '';
      }
    },
    [anchorValue, formatValue]
  );

  const clickDragRef = useClickDrag(
    useCallback(
      ({ event }) => {
        if (disabled) return;
        event.stopPropagation();
        event.preventDefault();
        const faderBackgroundRect =
          faderBackgroundRef.current?.getBoundingClientRect();
        if (!faderBackgroundRect) return;
        const initialValue = value;
        const faderBackgroundWidth =
          faderBackgroundRef.current?.clientWidth || 150;
        const unsetCursor = setCursor('ew-resize');
        let totalMovementX = 0;
        let isDrag = false;
        let wasDrag = false;
        return {
          onMouseMove: ({ deltaXFromLast, event }) => {
            event.stopPropagation();
            event.preventDefault();
            if (event instanceof MouseEvent) {
              if (Math.abs(event.movementX) < 100) {
                totalMovementX += event.movementX / 4;
              }
            } else {
              totalMovementX += deltaXFromLast;
            }
            if (Math.abs(totalMovementX) > 0 && !isDrag) {
              isDrag = true;
              document.body.requestPointerLock();
              setIsDragging(true);
            }
            if (isDrag) {
              const newValue = Math.min(
                1,
                Math.max(
                  0,
                  initialValue + totalMovementX / faderBackgroundWidth
                )
              );
              onChange?.(newValue, !wasDrag);
              updateDisplay(newValue);
              wasDrag = true;
            }
          },
          onMouseUp: ({ event }) => {
            event.stopPropagation();
            event.preventDefault();
            document.exitPointerLock();
            unsetCursor();
            setIsDragging(false);
            if (!isDrag) return;
            const newValue = Math.min(
              1,
              Math.max(0, initialValue + totalMovementX / faderBackgroundWidth)
            );
            onCommit?.(newValue);
            updateDisplay(newValue);
          },
        };
      },
      [value, onCommit, onChange, disabled, updateDisplay]
    )
  );

  useEffect(() => {
    if (!isDragging) updateDisplay(value);
  }, [isDragging, value, updateDisplay]);

  const anchorStyle = useMemo(
    () => ({ left: anchorValue * 100 + '%' }),
    [anchorValue]
  );

  return (
    <FaderInteractionArea
      className={isDragging ? 'dragging' : ''}
      style={{
        opacity: disabled ? 0.5 : 1,
      }}
      ref={clickDragRef}
      onDoubleClick={() => {
        if (disabled) return;
        if (defaultValue !== undefined) {
          onChange?.(defaultValue, true);
          onCommit?.(defaultValue);
          updateDisplay(defaultValue);
        }
      }}
    >
      <FaderBackground ref={faderBackgroundRef} hasMeter={!!getMeterValue}>
        <FaderForeground
          style={getStyleObject(value, anchorValue)}
          ref={faderForegroundRef}
        >
          <FaderKnob
            className='fader-knob'
            ref={faderKnobRef}
            style={{
              top: getMeterValue ? -4 : -8.5,
              ...(value < anchorValue ? { left: -5 } : { right: -5 }),
            }}
          >
            <AnchoredTooltip
              placement={tooltipPlacement}
              keepOpen={isDragging}
              label={
                formatValue && (
                  <FaderValueDisplay
                    className='fader-value-display'
                    ref={faderValueDisplayRef}
                  >
                    {formatValue(value)}
                  </FaderValueDisplay>
                )
              }
            >
              <TooltipTouchTarget />
            </AnchoredTooltip>
          </FaderKnob>
        </FaderForeground>
        {getMeterValue ? (
          <>
            <MeterAnchorDisplay
              style={anchorStyle}
              className='hover-emphasize'
            />
            <MeterRenderer
              getMeterValue={getMeterValue}
              color={color}
              warnOnClip={warnOnClip}
            />
          </>
        ) : (
          <BackgroundSegmentWrapper>
            <BackgroundSegmentDisplay>
              <BackgroundSegmentDisplayInner
                style={{
                  right: -5,
                }}
                ref={preAnchorRef}
              />
            </BackgroundSegmentDisplay>
            <AnchorDisplay />
            <BackgroundSegmentDisplay>
              <BackgroundSegmentDisplayInner
                style={{
                  left: -5,
                }}
                ref={postAnchorRef}
              />
            </BackgroundSegmentDisplay>
          </BackgroundSegmentWrapper>
        )}
      </FaderBackground>
    </FaderInteractionArea>
  );
}
