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

import { useContextSelector } from '@/hooks/useContextSelector';

import StudioContext from '../../studio/StudioContext';
import updateTrack from '../../studio/actions/updateTrack';
import { EQBand, StudioTrack } from '../../studio/types';

// Unified key system for bands (now all bands, no separate filters)
export type EQKey = 'band1' | 'band2' | 'band3' | 'band4' | 'band5' | 'band6';
export type BandKey = EQKey;

// Legacy filter keys for backward compatibility
export type FilterKey = 'highPassFilter' | 'lowPassFilter';

// Array of all keys for iteration
export const BAND_KEYS: BandKey[] = [
  'band1',
  'band2',
  'band3',
  'band4',
  'band5',
  'band6',
];
export const ALL_EQ_KEYS: EQKey[] = BAND_KEYS;

// Unified EQ value type - now just EQBand since all are bands with types
export type EQValue = EQBand;

// Map of all EQ values keyed by their keys (same as TrackEQ structure)
export type EQValues = {
  band1: EQBand;
  band2: EQBand;
  band3: EQBand;
  band4: EQBand;
  band5: EQBand;
  band6: EQBand;
};

// Map keys to their types (all are now 'band' with internal type field)
export const EQ_KEY_TYPE_MAP: Record<EQKey, 'band'> = {
  band1: 'band',
  band2: 'band',
  band3: 'band',
  band4: 'band',
  band5: 'band',
  band6: 'band',
};

// Get the type of an EQ key (now all are bands)
export function isFilterKey(key: EQKey): key is never {
  return false; // All keys are now bands with internal type field
}

export function isBandKey(key: EQKey): key is BandKey {
  return true; // All keys are now bands
}

// Numeric values that change rapidly (stored in refs for performance)
export type EQNumericValues = {
  frequency: number;
  q: number;
  gain: number;
};

export type EQStateManagement = {
  // Current selected key (read directly from local state, set immediately)
  selectedKey: EQKey;
  setSelectedKey: (key: EQKey) => void;

  // Get a band's complete current value (combines project state with realtime refs)
  getBandValue: (key: EQKey) => EQBand;

  // Get the currently selected band value
  getSelectedValue: () => EQBand;

  // Get numeric values for a band (from refs, updates rapidly)
  getNumericValues: (key: EQKey) => EQNumericValues;

  // Update numeric values in real-time (only updates refs and DSP, no state changes)
  updateNumericValuesRealtime: (
    key: EQKey,
    updates: Partial<EQNumericValues>
  ) => void;

  // Update non-numeric properties immediately (updates project state directly)
  updateBandProperties: (
    key: EQKey,
    updates: Partial<Omit<EQBand, 'frequency' | 'q' | 'gain'>>
  ) => void;

  // Commit numeric changes (writes refs to project state)
  commitNumericChanges: (key: EQKey) => void;

  // Select first enabled band if current selection is disabled (call explicitly after preset changes)
  // Pass the new EQ values to check against (since state update is async)
  selectFirstEnabledBandIfNeeded: (newEQ: EQValues) => void;
};

export function useEQStateManagement(track: StudioTrack): EQStateManagement {
  const [selectedKey, setSelectedKey] = useState<EQKey>('band1');

  const setState = useContextSelector(StudioContext, (ctx) => ctx?.setState);
  const playbackController = useContextSelector(
    StudioContext,
    (ctx) => ctx?.playbackController
  );

  // Refs for numeric values that change rapidly (frequency, q, gain)
  // These are updated immediately during interaction and NOT stored in React state
  const numericValuesRef = useRef<Record<EQKey, EQNumericValues>>({
    band1: {
      frequency: track.eq.band1.frequency,
      q: track.eq.band1.q,
      gain: track.eq.band1.gain,
    },
    band2: {
      frequency: track.eq.band2.frequency,
      q: track.eq.band2.q,
      gain: track.eq.band2.gain,
    },
    band3: {
      frequency: track.eq.band3.frequency,
      q: track.eq.band3.q,
      gain: track.eq.band3.gain,
    },
    band4: {
      frequency: track.eq.band4.frequency,
      q: track.eq.band4.q,
      gain: track.eq.band4.gain,
    },
    band5: {
      frequency: track.eq.band5.frequency,
      q: track.eq.band5.q,
      gain: track.eq.band5.gain,
    },
    band6: {
      frequency: track.eq.band6.frequency,
      q: track.eq.band6.q,
      gain: track.eq.band6.gain,
    },
  });

  // Cache for band objects to ensure stable references when values don't change
  // This prevents unnecessary re-renders in useRealtimeValue
  const bandCacheRef = useRef<Record<EQKey, EQBand>>(
    {} as Record<EQKey, EQBand>
  );

  // Sync refs when track.eq changes (e.g., preset applied, undo/redo)
  const trackEQRef = useRef(track.eq);
  useEffect(() => {
    if (trackEQRef.current !== track.eq) {
      // Update all numeric refs from new project state
      BAND_KEYS.forEach((key) => {
        numericValuesRef.current[key] = {
          frequency: track.eq[key].frequency,
          q: track.eq[key].q,
          gain: track.eq[key].gain,
        };
      });
      // Clear cache to force re-creation with new values
      bandCacheRef.current = {} as Record<EQKey, EQBand>;
      trackEQRef.current = track.eq;
    }
  }, [track.eq]);

  // Get numeric values for a band (from refs)
  const getNumericValues = useCallback((key: EQKey): EQNumericValues => {
    return numericValuesRef.current[key];
  }, []);

  // Get complete band value (combines project state with realtime numeric refs)
  // Returns cached object if values haven't changed to prevent unnecessary re-renders
  const getBandValue = useCallback(
    (key: EQKey): EQBand => {
      const bandFromState = track.eq[key];
      const numericValues = numericValuesRef.current[key];
      const cached = bandCacheRef.current[key];

      // Check if we can return cached value (all properties match)
      if (
        cached &&
        cached.type === bandFromState.type &&
        cached.enabled === bandFromState.enabled &&
        cached.frequency === numericValues.frequency &&
        cached.q === numericValues.q &&
        cached.gain === numericValues.gain
      ) {
        return cached;
      }

      // Create new object only if values changed
      const newBand: EQBand = {
        ...bandFromState,
        ...numericValues,
      };
      bandCacheRef.current[key] = newBand;
      return newBand;
    },
    [track.eq]
  );

  // Get the currently selected value
  const getSelectedValue = useCallback((): EQBand => {
    return getBandValue(selectedKey);
  }, [selectedKey, getBandValue]);

  // Update numeric values in real-time (only updates refs and DSP, no state changes)
  const updateNumericValuesRealtime = useCallback(
    (key: EQKey, updates: Partial<EQNumericValues>) => {
      // Update ref immediately
      numericValuesRef.current[key] = {
        ...numericValuesRef.current[key],
        ...updates,
      };

      // Get complete band value for DSP update
      const band = getBandValue(key);

      // Update DSP engine immediately for real-time audio
      if (playbackController?.updateTrackEQ) {
        playbackController.updateTrackEQ(
          track.id,
          key as BandKey,
          band.frequency,
          band.q,
          band.gain,
          band.type,
          band.enabled
        );
      }
    },
    [track.id, playbackController, getBandValue]
  );

  // Update non-numeric properties immediately (updates project state directly)
  const updateBandProperties = useCallback(
    (
      key: EQKey,
      updates: Partial<Omit<EQBand, 'frequency' | 'q' | 'gain'>>
    ) => {
      if (!setState) return;

      setState(
        updateTrack(track.id, (t) => ({
          ...t,
          eq: {
            ...t.eq,
            enabled: true, // Enable EQ when changes are made
            [key]: {
              ...t.eq[key],
              ...updates,
            },
          },
        }))
      );

      // Also update DSP engine if enabled status changed
      if (updates.enabled !== undefined || updates.type !== undefined) {
        const band = getBandValue(key);
        if (playbackController?.updateTrackEQ) {
          playbackController.updateTrackEQ(
            track.id,
            key as BandKey,
            band.frequency,
            band.q,
            band.gain,
            updates.type !== undefined ? updates.type : band.type,
            updates.enabled !== undefined ? updates.enabled : band.enabled
          );
        }
      }
    },
    [track.id, setState, playbackController, getBandValue]
  );

  // Commit numeric changes (writes refs to project state)
  const commitNumericChanges = useCallback(
    (key: EQKey) => {
      if (!setState) return;

      const numericValues = numericValuesRef.current[key];

      setState(
        updateTrack(track.id, (t) => ({
          ...t,
          eq: {
            ...t.eq,
            enabled: true, // Enable EQ when changes are made
            [key]: {
              ...t.eq[key],
              ...numericValues,
            },
          },
        }))
      );
    },
    [track.id, setState]
  );

  // Select first enabled band if current selection is disabled
  // This is called explicitly after preset changes
  const selectFirstEnabledBandIfNeeded = useCallback(
    (newEQ: EQValues) => {
      const selectedBand = newEQ[selectedKey] as EQBand;
      if (!selectedBand.enabled) {
        // Find the first enabled band
        const firstEnabledKey = BAND_KEYS.find((key) => {
          const band = newEQ[key] as EQBand;
          return band.enabled;
        });

        if (firstEnabledKey) {
          setSelectedKey(firstEnabledKey);
        }
      }
    },
    [selectedKey]
  );

  return useMemo(
    () => ({
      selectedKey,
      setSelectedKey,
      getBandValue,
      getSelectedValue,
      getNumericValues,
      updateNumericValuesRealtime,
      updateBandProperties,
      commitNumericChanges,
      selectFirstEnabledBandIfNeeded,
    }),
    [
      selectedKey,
      setSelectedKey,
      getBandValue,
      getSelectedValue,
      getNumericValues,
      updateNumericValuesRealtime,
      updateBandProperties,
      commitNumericChanges,
      selectFirstEnabledBandIfNeeded,
    ]
  );
}
