// js/equalizer.js
// Parametric Equalizer with Web Audio API - Supports 3-32 bands

import { equalizerSettings } from './storage.js';

// Standard 16-band ISO center frequencies (Hz) - kept for reference
const DEFAULT_EQ_FREQUENCIES = [25, 40, 63, 100, 160, 250, 400, 630, 1000, 1600, 2500, 4000, 6300, 10000, 16000, 20000];

// Frequency labels for UI display
const DEFAULT_FREQUENCY_LABELS = [
    '25',
    '40',
    '63',
    '100',
    '160',
    '250',
    '400',
    '630',
    '1K',
    '1.6K',
    '2.5K',
    '4K',
    '6.3K',
    '10K',
    '16K',
    '20K',
];

// Generate frequency array for given number of bands using logarithmic spacing
function generateFrequencies(bandCount, minFreq = 20, maxFreq = 20000) {
    const frequencies = [];
    const safeMin = Math.max(10, minFreq);
    const safeMax = Math.min(96000, maxFreq);

    for (let i = 0; i < bandCount; i++) {
        // Logarithmic interpolation
        const t = i / (bandCount - 1);
        const freq = safeMin * Math.pow(safeMax / safeMin, t);
        frequencies.push(Math.round(freq));
    }

    return frequencies;
}

// Generate frequency labels for display
function generateFrequencyLabels(frequencies) {
    return frequencies.map((freq) => {
        if (freq < 1000) {
            return freq.toString();
        } else if (freq < 10000) {
            return (freq / 1000).toFixed(freq % 1000 === 0 ? 0 : 1) + 'K';
        } else {
            return (freq / 1000).toFixed(0) + 'K';
        }
    });
}

// EQ Presets (gain values in dB for each of the 16 bands)
const EQ_PRESETS_16BAND = {
    flat: {
        name: 'Flat',
        gains: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    },
    bass_boost: {
        name: 'Bass Boost',
        gains: [6, 5, 4.5, 4, 3, 2, 1, 0.5, 0, 0, 0, 0, 0, 0, 0, 0],
    },
    bass_reducer: {
        name: 'Bass Reducer',
        gains: [-6, -5, -4, -3, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    },
    treble_boost: {
        name: 'Treble Boost',
        gains: [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 5.5, 6],
    },
    treble_reducer: {
        name: 'Treble Reducer',
        gains: [0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -4, -5, -5.5, -6],
    },
    vocal_boost: {
        name: 'Vocal Boost',
        gains: [-2, -1, 0, 0, 1, 2, 3, 4, 4, 3, 2, 1, 0, 0, -1, -2],
    },
    loudness: {
        name: 'Loudness',
        gains: [5, 4, 3, 1, 0, -1, -1, 0, 0, 1, 2, 3, 4, 4.5, 4, 3],
    },
    rock: {
        name: 'Rock',
        gains: [4, 3.5, 3, 2, -1, -2, -1, 1, 2, 3, 3.5, 4, 4, 3, 2, 1],
    },
    pop: {
        name: 'Pop',
        gains: [-1, 0, 1, 2, 3, 3, 2, 1, 0, 1, 2, 2, 2, 2, 1, 0],
    },
    classical: {
        name: 'Classical',
        gains: [3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 2],
    },
    jazz: {
        name: 'Jazz',
        gains: [3, 2, 1, 1, -1, -1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2],
    },
    electronic: {
        name: 'Electronic',
        gains: [4, 3.5, 3, 1, 0, -1, 0, 1, 2, 3, 3, 2, 2, 3, 4, 3.5],
    },
    hip_hop: {
        name: 'Hip-Hop',
        gains: [5, 4.5, 4, 3, 1, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2],
    },
    r_and_b: {
        name: 'R&B',
        gains: [3, 5, 4, 2, 1, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1],
    },
    acoustic: {
        name: 'Acoustic',
        gains: [3, 2, 1, 1, 2, 2, 1, 0, 0, 1, 1, 2, 3, 3, 2, 1],
    },
    podcast: {
        name: 'Podcast / Speech',
        gains: [-3, -2, -1, 0, 1, 2, 3, 4, 4, 3, 2, 1, 0, -1, -2, -3],
    },
};

// Interpolate 16-band preset to target band count
function interpolatePreset(preset16, targetBands) {
    if (targetBands === 16) return [...preset16];

    const result = [];
    for (let i = 0; i < targetBands; i++) {
        const sourceIndex = (i / (targetBands - 1)) * (preset16.length - 1);
        const indexLow = Math.floor(sourceIndex);
        const indexHigh = Math.min(Math.ceil(sourceIndex), preset16.length - 1);
        const fraction = sourceIndex - indexLow;

        const lowValue = preset16[indexLow] || 0;
        const highValue = preset16[indexHigh] || 0;
        const interpolated = lowValue + (highValue - lowValue) * fraction;
        result.push(Math.round(interpolated * 10) / 10);
    }
    return result;
}

// Get presets for given band count
function getPresetsForBandCount(bandCount) {
    const presets = {};
    for (const [key, preset] of Object.entries(EQ_PRESETS_16BAND)) {
        presets[key] = {
            name: preset.name,
            gains: interpolatePreset(preset.gains, bandCount),
        };
    }
    return presets;
}

export class Equalizer {
    constructor() {
        this.audioContext = null;
        this.source = null;
        this.filters = [];
        this.inputNode = null;
        this.outputNode = null;
        this.isEnabled = false;
        this.isInitialized = false;
        this.audio = null;

        // Band configuration
        this.bandCount = equalizerSettings.getBandCount();
        this.freqRange = equalizerSettings.getFreqRange();
        this.frequencies = generateFrequencies(this.bandCount, this.freqRange.min, this.freqRange.max);
        this.frequencyLabels = generateFrequencyLabels(this.frequencies);

        // Store current gains
        this.currentGains = new Array(this.bandCount).fill(0);

        // Store current preamp value
        this.preamp = 0;

        // Load saved settings
        this._loadSettings();
    }

    /**
     * Update band count and reinitialize
     */
    setBandCount(count) {
        const newCount = Math.max(
            equalizerSettings.MIN_BANDS,
            Math.min(equalizerSettings.MAX_BANDS, parseInt(count, 10) || 16)
        );

        if (newCount === this.bandCount) return;

        // Save new band count
        equalizerSettings.setBandCount(newCount);

        // Update configuration
        this.bandCount = newCount;
        this.frequencies = generateFrequencies(newCount, this.freqRange.min, this.freqRange.max);
        this.frequencyLabels = generateFrequencyLabels(this.frequencies);

        // Interpolate current gains to new band count
        const newGains = equalizerSettings.interpolateGains(this.currentGains, newCount);
        this.currentGains = newGains;
        equalizerSettings.setGains(newGains);

        // Reinitialize if already initialized
        if (this.isInitialized) {
            this.destroy();
            if (this.audioContext && this.source && this.audio) {
                this.init(this.audioContext, this.source, this.audio);
            }
        }

        // Dispatch event for UI update
        window.dispatchEvent(
            new CustomEvent('equalizer-band-count-changed', {
                detail: { bandCount: newCount, frequencies: this.frequencies, labels: this.frequencyLabels },
            })
        );
    }

    /**
     * Update frequency range and reinitialize
     */
    setFreqRange(minFreq, maxFreq) {
        const newMin = Math.max(10, Math.min(96000, parseInt(minFreq, 10) || 20));
        const newMax = Math.max(10, Math.min(96000, parseInt(maxFreq, 10) || 20000));

        if (newMin >= newMax) {
            console.warn('[Equalizer] Invalid frequency range: min must be less than max');
            return false;
        }

        if (newMin === this.freqRange.min && newMax === this.freqRange.max) return true;

        // Save new frequency range
        equalizerSettings.setFreqRange(newMin, newMax);

        // Update configuration
        this.freqRange = { min: newMin, max: newMax };
        this.frequencies = generateFrequencies(this.bandCount, newMin, newMax);
        this.frequencyLabels = generateFrequencyLabels(this.frequencies);

        // Reinitialize if already initialized
        if (this.isInitialized) {
            this.destroy();
            if (this.audioContext && this.source && this.audio) {
                this.init(this.audioContext, this.source, this.audio);
            }
        }

        // Dispatch event for UI update
        window.dispatchEvent(
            new CustomEvent('equalizer-freq-range-changed', {
                detail: { min: newMin, max: newMax, frequencies: this.frequencies, labels: this.frequencyLabels },
            })
        );

        return true;
    }

    /**
     * Initialize the equalizer with a shared AudioContext
     * This should be called after the visualizer creates the context
     * @param {AudioContext} audioContext - Shared audio context
     * @param {AudioNode} sourceNode - The MediaElementSource node
     * @param {HTMLAudioElement} audioElement - The audio element
     */
    init(audioContext, sourceNode, audioElement) {
        if (this.isInitialized) return;

        try {
            this.audioContext = audioContext;
            this.source = sourceNode;
            this.audio = audioElement;

            // Create biquad filters for each frequency band
            this.filters = this.frequencies.map((freq, index) => {
                const filter = this.audioContext.createBiquadFilter();

                // Use peaking filter for all bands (best for EQ)
                filter.type = 'peaking';
                filter.frequency.value = freq;
                filter.Q.value = this._calculateQ(index);
                filter.gain.value = this.currentGains[index] || 0;

                return filter;
            });

            // Create input/output gain nodes for bypass switching
            this.inputNode = this.audioContext.createGain();
            this.outputNode = this.audioContext.createGain();

            // Create preamp gain node
            this.preampNode = this.audioContext.createGain();
            this._updatePreampGain();

            // Connect the filter chain
            this._connectFilters();

            this.isInitialized = true;

            // Apply saved enabled state
            if (this.isEnabled) {
                this._enableFilters();
            }

            console.log(`[Equalizer] Initialized with ${this.bandCount} bands`);
        } catch (e) {
            console.warn('[Equalizer] Init failed:', e);
        }
    }

    /**
     * Calculate Q factor for each band
     * Using constant-Q design for consistent bandwidth
     */
    _calculateQ(_index) {
        // For 16-band 1/2 octave spacing, Q ≈ 2.87
        // Slightly lower Q for smoother response
        // Scale Q based on band count for consistent sound
        const baseQ = 2.5;
        const scalingFactor = Math.sqrt(16 / this.bandCount);
        return baseQ * scalingFactor;
    }

    /**
     * Connect all filters in series
     */
    _connectFilters() {
        if (!this.filters.length) return;

        // Connect preamp to first filter
        if (this.preampNode) {
            this.preampNode.connect(this.filters[0]);
        }

        // Chain filters together
        for (let i = 0; i < this.filters.length - 1; i++) {
            this.filters[i].connect(this.filters[i + 1]);
        }

        // Connect last filter to output
        this.filters[this.filters.length - 1].connect(this.outputNode);
    }

    /**
     * Enable the EQ processing
     */
    _enableFilters() {
        if (!this.isInitialized || !this.source) return;

        // Note: The actual connection handling is done by the visualizer
        // This just marks the EQ as enabled
        this.isEnabled = true;
    }

    /**
     * Disable the EQ (bypass)
     */
    _disableFilters() {
        this.isEnabled = false;
    }

    /**
     * Get the input node for external connection
     */
    getInputNode() {
        return this.preampNode || this.filters[0] || null;
    }

    /**
     * Get the output node
     */
    getOutputNode() {
        return this.outputNode;
    }

    /**
     * Check if EQ is active (enabled and initialized)
     */
    isActive() {
        return this.isInitialized && this.isEnabled;
    }

    /**
     * Toggle EQ on/off
     */
    toggle(enabled) {
        this.isEnabled = enabled;
        equalizerSettings.setEnabled(enabled);

        if (enabled) {
            this._enableFilters();
        } else {
            this._disableFilters();
        }

        // Dispatch event for visualizer to reconnect
        window.dispatchEvent(
            new CustomEvent('equalizer-toggle', {
                detail: { enabled },
            })
        );

        return this.isEnabled;
    }

    /**
     * Get current gain range from settings
     */
    getRange() {
        return equalizerSettings.getRange();
    }

    /**
     * Clamp gain to current range
     */
    _clampGain(gainDb) {
        const range = this.getRange();
        return Math.max(range.min, Math.min(range.max, gainDb));
    }

    /**
     * Set gain for a specific band
     * @param {number} bandIndex - Band index
     * @param {number} gainDb - Gain in dB
     */
    setBandGain(bandIndex, gainDb) {
        if (bandIndex < 0 || bandIndex >= this.bandCount) return;

        // Clamp gain to valid range
        const clampedGain = this._clampGain(gainDb);
        this.currentGains[bandIndex] = clampedGain;

        if (this.filters[bandIndex]) {
            // Smooth transition for clicks prevention
            const now = this.audioContext?.currentTime || 0;
            this.filters[bandIndex].gain.setTargetAtTime(clampedGain, now, 0.01);
        }

        // Save to storage
        equalizerSettings.setGains(this.currentGains);
    }

    /**
     * Set all band gains at once
     * @param {number[]} gains - Array of gain values in dB
     */
    setAllGains(gains) {
        if (!Array.isArray(gains)) return;

        // Ensure gains array matches current band count
        let adjustedGains = gains;
        if (gains.length !== this.bandCount) {
            adjustedGains = equalizerSettings.interpolateGains(gains, this.bandCount);
        }

        const now = this.audioContext?.currentTime || 0;

        adjustedGains.forEach((gain, index) => {
            const clampedGain = this._clampGain(gain);
            this.currentGains[index] = clampedGain;

            if (this.filters[index]) {
                this.filters[index].gain.setTargetAtTime(clampedGain, now, 0.01);
            }
        });

        equalizerSettings.setGains(this.currentGains);
    }

    /**
     * Apply a preset
     * @param {string} presetKey - Key from EQ_PRESETS
     */
    applyPreset(presetKey) {
        const presets = getPresetsForBandCount(this.bandCount);
        const preset = presets[presetKey];
        if (!preset) return;

        this.setAllGains(preset.gains);
        equalizerSettings.setPreset(presetKey);
    }

    /**
     * Reset all bands to flat (0 dB)
     */
    reset() {
        this.setAllGains(new Array(this.bandCount).fill(0));
        equalizerSettings.setPreset('flat');
    }

    /**
     * Get current gains
     * @returns {number[]} Array of gain values
     */
    getGains() {
        return [...this.currentGains];
    }

    /**
     * Get current band count
     * @returns {number} Number of bands
     */
    getBandCount() {
        return this.bandCount;
    }

    /**
     * Get frequency labels for UI
     * @returns {string[]} Array of frequency labels
     */
    getFrequencyLabels() {
        return this.frequencyLabels;
    }

    /**
     * Get frequencies
     * @returns {number[]} Array of frequency values
     */
    getFrequencies() {
        return this.frequencies;
    }

    /**
     * Get available presets (static method for default 16 bands)
     */
    static getPresets(bandCount = 16) {
        return getPresetsForBandCount(bandCount);
    }

    /**
     * Load settings from storage
     */
    _loadSettings() {
        this.isEnabled = equalizerSettings.isEnabled();
        this.bandCount = equalizerSettings.getBandCount();
        this.freqRange = equalizerSettings.getFreqRange();
        this.frequencies = generateFrequencies(this.bandCount, this.freqRange.min, this.freqRange.max);
        this.frequencyLabels = generateFrequencyLabels(this.frequencies);
        this.currentGains = equalizerSettings.getGains(this.bandCount);
        this.preamp = equalizerSettings.getPreamp();
    }

    /**
     * Update preamp gain value
     * @private
     */
    _updatePreampGain() {
        if (this.preampNode && this.audioContext) {
            const gainValue = Math.pow(10, this.preamp / 20);
            const now = this.audioContext.currentTime;
            this.preampNode.gain.setTargetAtTime(gainValue, now, 0.01);
        }
    }

    /**
     * Set preamp value in dB
     * @param {number} db - Preamp value in dB (-20 to +20)
     */
    setPreamp(db) {
        const clampedDb = Math.max(-20, Math.min(20, parseFloat(db) || 0));
        this.preamp = clampedDb;
        equalizerSettings.setPreamp(clampedDb);
        this._updatePreampGain();
    }

    /**
     * Get current preamp value
     * @returns {number} Current preamp value in dB
     */
    getPreamp() {
        return this.preamp;
    }

    /**
     * Destroy the equalizer
     */
    destroy() {
        this.filters.forEach((filter) => {
            try {
                filter.disconnect();
            } catch {
                /* ignore */
            }
        });

        try {
            this.inputNode?.disconnect();
        } catch {
            /* ignore */
        }
        try {
            this.outputNode?.disconnect();
        } catch {
            /* ignore */
        }
        try {
            this.preampNode?.disconnect();
        } catch {
            /* ignore */
        }

        this.filters = [];
        this.inputNode = null;
        this.outputNode = null;
        this.preampNode = null;
        this.isInitialized = false;
    }

    /**
     * Export equalizer settings to text format
     * @returns {string} Exported settings in text format
     */
    exportToText() {
        const lines = [];
        lines.push(`Preamp: ${this.preamp.toFixed(1)} dB`);

        this.frequencies.forEach((freq, index) => {
            const gain = this.currentGains[index] || 0;
            const type = this.currentTypes[index] || 'peaking';
            const typeMap = { peaking: 'PK', lowshelf: 'LSC', highshelf: 'HSC' };
            const typeStr = typeMap[type] || 'PK';
            const q = this.currentQs[index] || this._calculateQ(index);
            const filterNum = index + 1;
            lines.push(`Filter ${filterNum}: ON ${typeStr} Fc ${freq} Hz Gain ${gain.toFixed(1)} dB Q ${q.toFixed(2)}`);
        });

        return lines.join('\n');
    }

    /**
     * Import equalizer settings from text format
     * @param {string} text - Text format settings
     * @returns {boolean} True if import was successful
     */
    importFromText(text) {
        try {
            const lines = text
                .split('\n')
                .map((line) => line.trim())
                .filter((line) => line);
            const filters = [];
            let preamp = 0;

            for (const line of lines) {
                // Parse preamp
                const preampMatch = line.match(/^Preamp:\s*([+-]?\d+\.?\d*)\s*dB$/i);
                if (preampMatch) {
                    preamp = parseFloat(preampMatch[1]);
                    continue;
                }

                // Parse filter lines (handle "Filter:" and "Filter X:" formats)
                const filterMatch = line.match(
                    /^Filter\s*\d*:\s*ON\s+(\w+)\s+Fc\s+(\d+)\s+Hz\s+Gain\s*([+-]?\d+\.?\d*)\s*dB(?:\s+Q\s+(\d+\.?\d*))?/i
                );
                if (filterMatch) {
                    const type = filterMatch[1].toUpperCase();
                    const freq = parseInt(filterMatch[2], 10);
                    const gain = parseFloat(filterMatch[3]);
                    const q = filterMatch[4] ? parseFloat(filterMatch[4]) : Math.SQRT1_2;
                    filters.push({ type, freq, gain, q });
                }
            }

            if (filters.length === 0) {
                console.warn('[Equalizer] No valid filters found in import text');
                return false;
            }

            // Apply preamp
            this.setPreamp(preamp);

            // If different number of bands, adjust
            if (filters.length !== this.bandCount) {
                const newCount = Math.max(
                    equalizerSettings.MIN_BANDS,
                    Math.min(equalizerSettings.MAX_BANDS, filters.length)
                );
                this.setBandCount(newCount);
            }

            // Apply imported filter frequencies directly instead of regenerating
            const sliced = filters.slice(0, this.bandCount);
            const newFreqs = sliced.map((f) => f.freq);
            this.frequencies = newFreqs;
            this.frequencyLabels = generateFrequencyLabels(newFreqs);

            // Update filter frequencies on the actual biquad nodes
            if (this.filters.length === newFreqs.length) {
                newFreqs.forEach((freq, i) => {
                    if (this.filters[i]) {
                        this.filters[i].frequency.value = freq;
                    }
                });
            }

            // Extract and apply gains, types, and Qs
            const gains = sliced.map((f) => f.gain);
            this.setAllGains(gains);

            // Apply filter types (PK/LS/HS -> peaking/lowshelf/highshelf)
            const typeMap = { PK: 'peaking', LS: 'lowshelf', HS: 'highshelf', LSC: 'lowshelf', HSC: 'highshelf' };
            const types = sliced.map((f) => typeMap[f.type] || 'peaking');
            this.currentTypes = types;
            if (this.filters.length === types.length) {
                types.forEach((type, i) => {
                    if (this.filters[i]) this.filters[i].type = type;
                });
            }
            equalizerSettings.setBandTypes(types);

            // Apply Q values
            const qs = sliced.map((f) => f.q);
            this.currentQs = qs;
            if (this.filters.length === qs.length) {
                qs.forEach((q, i) => {
                    if (this.filters[i]) this.filters[i].Q.value = q;
                });
            }
            equalizerSettings.setBandQs(qs);

            // Persist custom frequencies and update freqRange
            equalizerSettings.setCustomFrequencies(newFreqs);
            const minFreq = Math.min(...newFreqs);
            const maxFreq = Math.max(...newFreqs);
            this.freqRange = { min: minFreq, max: maxFreq };
            equalizerSettings.setFreqRange(minFreq, maxFreq);

            return true;
        } catch (e) {
            console.warn('[Equalizer] Failed to import settings:', e);
            return false;
        }
    }
}

// Export singleton instance
export const equalizer = new Equalizer();

// Export helper functions and constants
export {
    generateFrequencies,
    generateFrequencyLabels,
    getPresetsForBandCount,
    interpolatePreset,
    DEFAULT_EQ_FREQUENCIES,
    DEFAULT_FREQUENCY_LABELS,
    EQ_PRESETS_16BAND as EQ_PRESETS,
};
