import React, { useCallback, useEffect, useRef, useState } from 'react';

import { useContextSelector } from '@/hooks/useContextSelector';
import { BufferF32 } from '@/utils/dsp';

import StudioContext from '../../studio/StudioContext';
import updateTrack from '../../studio/actions/updateTrack';
import { EQBand, StudioTrack } from '../../studio/types';
import DraggableEQBandDot from './DraggableEQBandDot';
import EQDisplayInfo from './EQDisplayInfo';
import { useEQState } from './EQStateContext';
import { drawCenterLine, drawEQCurvesOnContext, drawGrid } from './drawEQCurve';
import { EQCanvas, EQContainer } from './eqComponents';
import { DEFAULT_EQ_BANDS } from './eqPresets';
import { gainToPosition } from './eqUtils';
import traceSpectrum from './traceSpectrum';
import { BAND_KEYS, EQKey } from './useEQStateManagement';

export default function EQVisualization({ track }: { track: StudioTrack }) {
  const setState = useContextSelector(StudioContext, (ctx) => ctx?.setState);
  const playbackController = useContextSelector(
    StudioContext,
    (ctx) => ctx?.playbackController
  );
  const eqState = useEQState();
  const { getBandValue, setSelectedKey, selectedKey } = eqState;

  // Track which band is being dragged or hovered
  const [draggingKey, setDraggingKey] = useState<EQKey | null>(null);
  const [hoveredKey, setHoveredKey] = useState<EQKey | null>(null);

  const canvasRef = useRef<HTMLCanvasElement>(null);
  const containerRef = useRef<HTMLDivElement>(null);

  // FFT observer refs
  const fftBufferRef = useRef<BufferF32 | null>(null);
  const animationFrameRef = useRef<number | null>(null);
  const sampleRateRef = useRef<number>(48000); // Default, will be updated

  // Refs for Q mode (used for keyboard listener)
  const exitQModeRefs = useRef<((() => void) | null)[]>([
    null,
    null,
    null,
    null,
    null,
    null,
  ]);
  const isQModeRefs = useRef<boolean[]>([
    false,
    false,
    false,
    false,
    false,
    false,
  ]);

  // Update numeric values during drag
  const updateValueRealtime = useCallback(
    (key: EQKey, updates: Partial<EQBand>) => {
      // Extract numeric updates
      const numericUpdates: Partial<{
        frequency: number;
        q: number;
        gain: number;
      }> = {};
      if (updates.frequency !== undefined)
        numericUpdates.frequency = updates.frequency;
      if (updates.q !== undefined) numericUpdates.q = updates.q;
      if (updates.gain !== undefined) numericUpdates.gain = updates.gain;

      // Update numeric values if any
      if (Object.keys(numericUpdates).length > 0) {
        eqState.updateNumericValuesRealtime(key, numericUpdates);
      }

      // Update non-numeric properties directly
      const nonNumericUpdates: Partial<
        Omit<EQBand, 'frequency' | 'q' | 'gain'>
      > = {};
      if (updates.type !== undefined) nonNumericUpdates.type = updates.type;
      if (updates.enabled !== undefined)
        nonNumericUpdates.enabled = updates.enabled;

      if (Object.keys(nonNumericUpdates).length > 0) {
        eqState.updateBandProperties(key, nonNumericUpdates);
      }
    },
    [eqState]
  );

  // Enable EQ if not already enabled
  const enableEQ = useCallback(() => {
    if (!setState || track.eq.enabled) return;

    setState(
      updateTrack(track.id, (t) => ({
        ...t,
        eq: {
          ...t.eq,
          enabled: true,
        },
      }))
    );
  }, [track.eq.enabled, track.id, setState]);

  // Commit numeric changes to permanent state
  const commitChanges = useCallback(
    (key: EQKey) => {
      eqState.commitNumericChanges(key);
    },
    [eqState]
  );

  // Set up FFT observer on mount
  useEffect(() => {
    if (!playbackController) return;

    // Enable FFT observer for this track
    playbackController.setTrackFFTObserverEnabled?.(track.id, true);

    // Get the FFT buffer
    const fftBuffer = playbackController.getTrackFFTObserverBuffer?.(track.id);
    if (fftBuffer) {
      fftBufferRef.current = fftBuffer;
    }

    // Get sample rate from DSP context
    const audioContext = playbackController.dspContext?.getAudioContext?.();
    if (audioContext) {
      sampleRateRef.current = audioContext.sampleRate;
    }

    return () => {
      // Disable FFT observer on unmount
      playbackController.setTrackFFTObserverEnabled?.(track.id, false);
    };
  }, [track.id, playbackController]);

  // Animation loop to draw spectrum and EQ curve
  useEffect(() => {
    const FFT_SIZE = 1024;

    const animate = () => {
      const canvas = canvasRef.current;
      if (!canvas) {
        animationFrameRef.current = requestAnimationFrame(animate);
        return;
      }

      const ctx = canvas.getContext('2d');
      if (!ctx) {
        animationFrameRef.current = requestAnimationFrame(animate);
        return;
      }

      // Set up canvas with DPI scaling
      const dpr = window.devicePixelRatio || 1;
      const rect = canvas.getBoundingClientRect();
      canvas.width = rect.width * dpr;
      canvas.height = rect.height * dpr;
      ctx.scale(dpr, dpr);

      const width = rect.width;
      const height = rect.height;

      // Clear canvas
      ctx.clearRect(0, 0, width, height);

      // Calculate zero line position
      const zeroY = height * (1 - gainToPosition(0));

      // Draw background elements
      drawGrid(ctx, width, height);
      drawCenterLine(ctx, width, zeroY);

      // Draw spectrum if available
      const buffer = fftBufferRef.current;
      if (buffer) {
        // Read FFT data from the buffer
        const bufferData = buffer.view(0);

        // Draw the spectrum behind the EQ curve
        ctx.save();
        ctx.globalAlpha = 0.3;
        ctx.strokeStyle = '#ffffff66';
        ctx.fillStyle = '#383838ee';
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(-20, height);
        traceSpectrum(
          ctx,
          bufferData,
          FFT_SIZE,
          sampleRateRef.current,
          width,
          height,
          'quadratic'
        );
        ctx.lineTo(width, height);
        ctx.stroke();
        ctx.fill();
        ctx.restore();
      }

      // Draw EQ curves on top (without clearing)
      // Get all 6 bands using getBandValue to get latest values including refs
      const bands = [
        getBandValue('band1'),
        getBandValue('band2'),
        getBandValue('band3'),
        getBandValue('band4'),
        getBandValue('band5'),
        getBandValue('band6'),
      ];

      // Use dragging key directly for visualization (works for both gain-based and filter-based bands)
      const draggingBandKey:
        | 'band1'
        | 'band2'
        | 'band3'
        | 'band4'
        | 'band5'
        | 'band6'
        | null = draggingKey;

      drawEQCurvesOnContext(
        ctx,
        width,
        height,
        bands,
        draggingBandKey,
        track.eq.enabled
      );

      animationFrameRef.current = requestAnimationFrame(animate);
    };

    animate();

    return () => {
      // Cancel animation frame
      if (animationFrameRef.current !== null) {
        cancelAnimationFrame(animationFrameRef.current);
        animationFrameRef.current = null;
      }
    };
  }, [getBandValue, draggingKey, track.eq.enabled]);

  const handleBandDoubleClick = (bandKey: EQKey) => (e: React.MouseEvent) => {
    e.preventDefault();

    const bandIndex = parseInt(bandKey.replace('band', '')) - 1;
    const resetBand = DEFAULT_EQ_BANDS[bandIndex];

    // Update both local and context state
    updateValueRealtime(bandKey, resetBand);

    // Commit to permanent state
    if (setState) {
      setState(
        updateTrack(track.id, (t) => ({
          ...t,
          eq: {
            ...t.eq,
            [bandKey]: resetBand,
          },
        }))
      );
    }
  };

  // Listen for cmd/ctrl key release to exit Q mode
  useEffect(() => {
    const handleKeyUp = (e: KeyboardEvent) => {
      if (e.key === 'Meta' || e.key === 'Control') {
        // Exit Q mode for any band that has it active
        exitQModeRefs.current.forEach((exitQMode, index) => {
          if (exitQMode && isQModeRefs.current[index]) {
            exitQMode();
          }
        });
      }
    };

    window.addEventListener('keyup', handleKeyUp);
    return () => {
      window.removeEventListener('keyup', handleKeyUp);
    };
  }, []);

  // Cleanup pointer lock on unmount
  useEffect(() => {
    return () => {
      if (document.pointerLockElement === document.body) {
        document.exitPointerLock();
      }
    };
  }, []);

  // Determine which band to display info for
  // Priority: dragging > hovering
  const displayKey = draggingKey !== null ? draggingKey : hoveredKey;

  return (
    <>
      <EQContainer ref={containerRef}>
        <EQCanvas ref={canvasRef} />

        {/* Render all 6 bands uniformly - each can have its own type */}
        {BAND_KEYS.map((bandKey, index) => {
          // Check if band is enabled (read from project state directly)
          if (!track.eq[bandKey].enabled) {
            return null;
          }

          return (
            <DraggableEQBandDot
              key={bandKey}
              getBandValue={() => getBandValue(bandKey)}
              bandKey={bandKey}
              containerRef={containerRef}
              onDragStart={() => {
                enableEQ();
                setDraggingKey(bandKey);
                setSelectedKey(bandKey);
              }}
              onDragEnd={() => {
                commitChanges(bandKey);
                setDraggingKey(null);
              }}
              onUpdate={(updates) => updateValueRealtime(bandKey, updates)}
              onDoubleClick={handleBandDoubleClick(bandKey)}
              isHovered={hoveredKey === bandKey}
              isDragging={draggingKey === bandKey}
              isSelected={selectedKey === bandKey}
              onMouseEnter={() => setHoveredKey(bandKey)}
              onMouseLeave={() => setHoveredKey(null)}
              exitQModeRef={{
                get current() {
                  return exitQModeRefs.current[index];
                },
                set current(value) {
                  exitQModeRefs.current[index] = value;
                },
              }}
              isQModeRef={{
                get current() {
                  return isQModeRefs.current[index];
                },
                set current(value) {
                  isQModeRefs.current[index] = value;
                },
              }}
            />
          );
        })}
      </EQContainer>

      {/* Display info component handles its own mouse tracking */}
      <EQDisplayInfo
        containerRef={containerRef}
        displayKey={displayKey}
        getBandValue={getBandValue}
      />
    </>
  );
}
