import { Box, Flex, Text, useMantineTheme } from "@mantine/core";
import { useEffect, useRef, useState } from "react";

interface VoiceRecorderProps {
  duration: number;
  waves: number[];
}

type AnimatedBar = {
  id: number;
  value: number;
  entered: boolean;
};

const VISIBLE_BARS = 50;
const BAR_WIDTH = 3;
const BAR_GAP = 2;
const STEP_PX = BAR_WIDTH + BAR_GAP;
const COMPONENT_HEIGHT = 45;
const MAX_BAR_HEIGHT = 28;
const MIN_BAR_HEIGHT = 4;

export function VoiceRecorder(props: VoiceRecorderProps) {
  const theme = useMantineTheme();
  const [bars, setBars] = useState<AnimatedBar[]>([]);
  const [subShift, setSubShift] = useState(0);

  const prevLengthRef = useRef(0);
  const prevWavesRef = useRef<number[]>([]);
  const idRef = useRef(0);
  const enterFrameRef = useRef<number | null>(null);
  const scrollFrameRef = useRef<number | null>(null);

  const lastAppendAtRef = useRef<number | null>(null);
  const appendIntervalRef = useRef(120);
  const barsLengthRef = useRef(0);

  const formatDuration = (seconds: number) => {
    const mins = Math.floor(seconds / 60)
      .toString()
      .padStart(2, "0");
    const secs = (seconds % 60).toString().padStart(2, "0");
    return `${mins}:${secs}`;
  };

  useEffect(() => {
    barsLengthRef.current = bars.length;
  }, [bars.length]);

  useEffect(() => {
    if (props.waves.length === 0) {
      setBars([]);
      setSubShift(0);
      prevLengthRef.current = 0;
      prevWavesRef.current = [];
      lastAppendAtRef.current = null;
      return;
    }

    if (props.waves.length < prevLengthRef.current) {
      const resetBars = props.waves.slice(-VISIBLE_BARS).map((value) => ({
        id: idRef.current++,
        value,
        entered: true,
      }));

      setBars(resetBars);
      setSubShift(0);
      prevLengthRef.current = props.waves.length;
      prevWavesRef.current = props.waves;
      lastAppendAtRef.current = performance.now();
      return;
    }

    const prevWaves = prevWavesRef.current;
    let appended: number[] = [];

    // Обычный режим: длина выросла
    if (props.waves.length > prevLengthRef.current) {
      appended = props.waves.slice(prevLengthRef.current);
    } else if (props.waves.length === prevLengthRef.current && props.waves.length > 0) {
      // Rolling buffer: длина та же, но данные сдвигаются
      let changed = false;

      if (prevWaves.length !== props.waves.length) {
        changed = true;
      } else {
        for (let i = 0; i < props.waves.length; i++) {
          if (props.waves[i] !== prevWaves[i]) {
            changed = true;
            break;
          }
        }
      }

      if (changed) {
        appended = [props.waves[props.waves.length - 1]];
      }
    }

    if (appended.length > 0) {
      const now = performance.now();

      if (lastAppendAtRef.current != null) {
        const dt = now - lastAppendAtRef.current;
        const perBar = dt / appended.length;
        appendIntervalRef.current = appendIntervalRef.current * 0.7 + perBar * 0.3;
      }

      lastAppendAtRef.current = now;
      setSubShift(0);

      const newIds: number[] = [];

      setBars((prev) => {
        const next = [...prev];

        appended.forEach((value) => {
          const id = idRef.current++;
          newIds.push(id);

          next.push({
            id,
            value,
            entered: false,
          });
        });

        return next.slice(-VISIBLE_BARS);
      });

      if (enterFrameRef.current) {
        cancelAnimationFrame(enterFrameRef.current);
      }

      enterFrameRef.current = requestAnimationFrame(() => {
        setBars((prev) => {
          const ids = new Set(newIds);
          return prev.map((bar) => (ids.has(bar.id) ? { ...bar, entered: true } : bar));
        });
      });
    }

    prevLengthRef.current = props.waves.length;
    prevWavesRef.current = props.waves;
  }, [props.waves]);

  useEffect(() => {
    const tick = () => {
      const startedAt = lastAppendAtRef.current;

      if (startedAt != null) {
        const elapsed = performance.now() - startedAt;
        const interval = Math.max(16, appendIntervalRef.current);
        const progress = Math.min(1, elapsed / interval);

        const smoothShift = barsLengthRef.current >= VISIBLE_BARS ? -progress * STEP_PX : 0;
        setSubShift(smoothShift);
      } else {
        setSubShift(0);
      }

      scrollFrameRef.current = requestAnimationFrame(tick);
    };

    scrollFrameRef.current = requestAnimationFrame(tick);

    return () => {
      if (scrollFrameRef.current) {
        cancelAnimationFrame(scrollFrameRef.current);
      }
    };
  }, []);

  useEffect(() => {
    return () => {
      if (enterFrameRef.current) {
        cancelAnimationFrame(enterFrameRef.current);
      }
      if (scrollFrameRef.current) {
        cancelAnimationFrame(scrollFrameRef.current);
      }
    };
  }, []);

  const waveformWidth = VISIBLE_BARS * BAR_WIDTH + (VISIBLE_BARS - 1) * BAR_GAP;

  return (
    <Flex
      direction="row"
      h={COMPONENT_HEIGHT}
      mih={COMPONENT_HEIGHT}
      mah={COMPONENT_HEIGHT}
      align="center"
      justify="center"
      gap="xs"
      px={6}
    >
      <Text size="xs" c="dimmed" w={36}>
        {formatDuration(props.duration)}
      </Text>

      <Box
        w={waveformWidth}
        h={COMPONENT_HEIGHT}
        style={{
          overflow: "hidden",
        }}
      >
        <Flex
          h="100%"
          align="center"
          gap={BAR_GAP}
          wrap="nowrap"
          style={{ transform: `translateX(${subShift}px)` }}
        >
          {Array.from({ length: VISIBLE_BARS }).map((_, index) => {
            const bar = bars[index];

            if (!bar) {
              return (
                <Box
                  key={`empty-${index}`}
                  w={BAR_WIDTH}
                  h={MIN_BAR_HEIGHT}
                  style={{
                    flex: `0 0 ${BAR_WIDTH}px`,
                    borderRadius: 999,
                    background: theme.colors.gray[3],
                    opacity: 0.22,
                  }}
                />
              );
            }

            const normalized = Math.max(0, Math.min(1, bar.value));
            const height = Math.max(
              MIN_BAR_HEIGHT,
              Math.min(MAX_BAR_HEIGHT, MIN_BAR_HEIGHT + normalized * (MAX_BAR_HEIGHT - MIN_BAR_HEIGHT))
            );
            const isLast = index === bars.length - 1;
            const isNearTail = index >= bars.length - 3;

            return (
              <Box
                key={bar.id}
                w={BAR_WIDTH}
                h={height}
                style={{
                  flex: `0 0 ${BAR_WIDTH}px`,
                  alignSelf: "center",
                  borderRadius: 999,
                  background: isLast
                    ? `linear-gradient(180deg, ${theme.colors.blue[3]} 0%, ${theme.colors.blue[5]} 100%)`
                    : `linear-gradient(180deg, ${theme.colors.blue[4]} 0%, ${theme.colors.blue[6]} 100%)`,
                  boxShadow: isLast
                    ? `0 0 10px ${theme.colors.blue[4]}55`
                    : isNearTail
                      ? `0 0 6px ${theme.colors.blue[4]}22`
                      : "none",
                  transform: bar.entered ? "scaleY(1)" : "scaleY(0.18)",
                  transformOrigin: "center center",
                  transition: [
                    "height 260ms cubic-bezier(0.2, 0.8, 0.2, 1)",
                    "transform 260ms cubic-bezier(0.2, 0.8, 0.2, 1)",
                    "opacity 220ms ease",
                    "box-shadow 220ms ease",
                  ].join(", "),
                  willChange: "height, transform, opacity",
                }}
              />
            );
          })}
        </Flex>
      </Box>
    </Flex>
  );
}