export class LCDPreset {
    constructor() {
        this.name = 'LCD Pixels';
        this.gridCols = 48;

        // Auto-gain tracking
        this.maxVol = 100;
        this.volDecay = 0.995;

        // Smoothing state
        this.prevData = new Float32Array(this.gridCols).fill(0);
        this.peakData = new Float32Array(this.gridCols).fill(0);

        this.primaryColor = '#ffffff';
        this.disableShake = false;

        // WebGL grid overlay
        this.glCanvas = null;
        this.gl = null;
        this.glProgram = null;
        this.glInitialized = false;
    }

    // Initialize WebGL grid overlay
    initWebGL(width, height) {
        if (this.glInitialized) return;

        // Create overlay canvas
        this.glCanvas = document.createElement('canvas');
        this.glCanvas.width = width;
        this.glCanvas.height = height;
        this.glCanvas.style.cssText = 'position:absolute;top:0;left:0;pointer-events:none;mix-blend-mode:multiply;';

        const gl = this.glCanvas.getContext('webgl', { alpha: true, premultipliedAlpha: false });
        if (!gl) {
            console.warn('WebGL not available for grid overlay');
            return;
        }
        this.gl = gl;

        // Vertex shader (fullscreen quad)
        const vsSource = `
            attribute vec2 a_position;
            varying vec2 v_uv;
            void main() {
                v_uv = a_position * 0.5 + 0.5;
                gl_Position = vec4(a_position, 0.0, 1.0);
            }
        `;

        // Fragment shader (LCD dot matrix with tilt-shift blur)
        const fsSource = `
            precision highp float;
            varying vec2 v_uv;
            uniform vec2 u_resolution;
            uniform float u_time;
            
            float hash(vec2 p) {
                return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
            }
            
            void main() {
                vec2 uv = v_uv;
                float aspect = u_resolution.x / u_resolution.y;
                
                // Skew transform
                vec2 centered = uv - 0.5;
                mat2 skewMatrix = mat2(1.0, 0.0, 0.20, 1.0);
                vec2 skewed = skewMatrix * centered + 0.5;
                
                // Perspective: shrink towards right
                float perspT = skewed.x;
                float perspScale = mix(1.0, 0.5, perspT);
                
                // Tilt-shift: focus at 25%, blur both near (left) and far (right)
                float focusPoint = 0.25;
                float distFromFocus = abs(perspT - focusPoint);
                float blurAmount = smoothstep(0.0, 0.6, distFromFocus);
                
                // Apply perspective
                vec2 pUV = skewed;
                pUV.y = (pUV.y - 0.5) * perspScale + 0.5;
                pUV.x *= aspect;
                
                // Dot matrix grid
                float cellSize = 0.0078 * perspScale;
                vec2 gridUV = pUV / cellSize;
                vec2 gv = fract(gridUV) - 0.5;
                vec2 id = floor(gridUV);
                
                float d = length(gv);
                float dotRadius = 0.35;
                
                // Dot edge with blur (pattern stays visible)
                float sharpness = mix(0.08, 0.25, blurAmount);
                float dotEdge = smoothstep(dotRadius - sharpness, dotRadius + sharpness * 0.3, d);
                
                // Per-cell noise
                float noise = hash(id);
                dotEdge *= 0.75 + noise * 0.25;
                
                // Subtle grain
                float grain = hash(uv * u_resolution + u_time) * 0.015;
                
                // Output
                float alpha = clamp(dotEdge * 0.5 + grain, 0.0, 0.5);
                gl_FragColor = vec4(0.0, 0.0, 0.0, alpha);
            }
        `;

        // Compile shaders
        const vs = this.compileShader(gl, gl.VERTEX_SHADER, vsSource);
        const fs = this.compileShader(gl, gl.FRAGMENT_SHADER, fsSource);
        if (!vs || !fs) return;

        // Link program
        const program = gl.createProgram();
        gl.attachShader(program, vs);
        gl.attachShader(program, fs);
        gl.linkProgram(program);

        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            console.error('Shader program failed to link');
            return;
        }

        this.glProgram = program;

        // Create fullscreen quad
        const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
        const buffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

        const posLoc = gl.getAttribLocation(program, 'a_position');
        gl.enableVertexAttribArray(posLoc);
        gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);

        // Store uniform locations
        this.uResolution = gl.getUniformLocation(program, 'u_resolution');
        this.uTime = gl.getUniformLocation(program, 'u_time');

        gl.useProgram(program);
        gl.enable(gl.BLEND);
        gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

        this.startTime = performance.now();
        this.glInitialized = true;
    }

    compileShader(gl, type, source) {
        const shader = gl.createShader(type);
        gl.shaderSource(shader, source);
        gl.compileShader(shader);
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            console.error('Shader compile error:', gl.getShaderInfoLog(shader));
            gl.deleteShader(shader);
            return null;
        }
        return shader;
    }

    // Render WebGL grid overlay
    renderHoneycomb(width, height) {
        if (!this.gl || !this.glProgram) return;

        const gl = this.gl;

        // Resize if needed
        if (this.glCanvas.width !== width || this.glCanvas.height !== height) {
            this.glCanvas.width = width;
            this.glCanvas.height = height;
            gl.viewport(0, 0, width, height);
        }

        gl.clearColor(0, 0, 0, 0);
        gl.clear(gl.COLOR_BUFFER_BIT);

        // Pass uniforms
        gl.uniform2f(this.uResolution, width, height);
        gl.uniform1f(this.uTime, (performance.now() - this.startTime) / 1000.0);

        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    }

    resize() {}

    draw(ctx, canvas, analyser, dataArray, params) {
        const { width, height } = canvas;
        const { kick, primaryColor, mode } = params;

        this.primaryColor = primaryColor;
        const isDark = document.documentElement.getAttribute('data-theme') !== 'white';

        // --- Background ---
        ctx.clearRect(0, 0, width, height);
        if (mode !== 'blended') {
            ctx.fillStyle = isDark ? '#050505' : '#e6e6e6';
            ctx.fillRect(0, 0, width, height);
        }

        // --- Audio Data Processing ---
        const data = this.processAudio(dataArray, analyser);

        // --- Perspective Constants ---
        const centerX = width / 2;
        const centerY = height * 0.35;
        const startX = width * 0.05;
        const endX = width * 0.95;
        const totalW = endX - startX;
        const maxBarH = height;
        const startScale = 2.0; // Left (near) - increased
        const endScale = 0.05; // Right (far) - decreased

        // --- Apply Global Skew Transform ---
        ctx.save();
        ctx.translate(centerX, centerY);
        ctx.transform(1, -0.08, 0.2, 1, 0, 0);
        ctx.translate(-centerX, -centerY);

        // Shake on kick
        if (!this.disableShake && kick > 0.1) {
            const shake = kick * 8;
            ctx.translate((Math.random() - 0.5) * 2 * shake, (Math.random() - 0.5) * shake);
        }

        // --- Draw Bars ---
        const baseBarW = (totalW / this.gridCols) * 0.7; // Base width

        for (let c = 0; c < this.gridCols; c++) {
            const p = c / (this.gridCols - 1);

            // Simple perspective: scale goes from startScale (left) to endScale (right)
            const scale = startScale + (endScale - startScale) * p;

            // Perspective spacing: gaps decrease linearly matching the scale
            // Integral of linear scale function, normalized to 0-1
            const scaleDelta = endScale - startScale;
            const pIntegral = startScale * p + 0.5 * scaleDelta * p * p;
            const totalIntegral = startScale + 0.5 * scaleDelta; // Value at p=1
            const pPerspective = pIntegral / totalIntegral;
            const cx = startX + pPerspective * totalW;

            // Width scales with perspective
            const barW = baseBarW * scale;

            // Bar height - skip empty bars entirely
            const normVal = data[c];
            if (normVal < 0.01) continue;

            const h = normVal * maxBarH * scale;
            if (h < 1) continue;

            // Per-bar color variation
            const variation = 0.75 + Math.abs(Math.sin(c * 127.1)) * 0.25;
            ctx.fillStyle = this.adjustBrightness(primaryColor, variation);

            // Strong LCD light bleed effect
            ctx.shadowBlur = 30 + normVal * 50; // Increased glow
            ctx.shadowColor = primaryColor;
            this.drawCapsule(ctx, cx, centerY, barW, h);
        }

        ctx.restore();

        // --- WebGL grid Overlay ---
        // Initialize on first run
        if (!this.glInitialized) {
            this.initWebGL(width, height);
            // Attach WebGL canvas to same parent as main canvas
            if (this.glCanvas && canvas.parentElement) {
                //This position:relative was causing the visual bugs and problems in the lcd visualiser.
                // canvas.parentElement.style.position = 'relative';
                canvas.parentElement.appendChild(this.glCanvas);
            }
        }

        // Render and composite grid
        this.renderHoneycomb(width, height);
    }

    // Process audio with improved dynamics
    processAudio(dataArray, analyser) {
        const result = new Float32Array(this.gridCols);
        const center = Math.floor(this.gridCols / 2);
        const totalBins = dataArray.length;
        let peakVal = 0;

        // Sample rate and bin size
        const sampleRate = analyser?.context?.sampleRate || 48000;
        const binSize = sampleRate / (totalBins * 2);

        // Define frequency range to map
        const minFreq = 40; // Start at 40Hz
        const maxFreq = 22000; // End at 22kHz

        for (let i = 0; i < center; i++) {
            const p = i / (center - 1);

            // Logarithmic frequency mapping: F = min * (max/min)^p
            const targetStartFreq = minFreq * Math.pow(maxFreq / minFreq, p);
            // Calculate next frequency to determine bandwidth of this bar
            const pNext = (i + 1) / (center - 1);
            const targetEndFreq = minFreq * Math.pow(maxFreq / minFreq, pNext); // Use pNext for end freq

            // Convert frequencies to bin indices
            const startBin = Math.max(1, Math.floor(targetStartFreq / binSize));
            const endBin = Math.max(startBin + 1, Math.floor(targetEndFreq / binSize));

            let sum = 0,
                count = 0;

            // Sum bins for this column
            for (let k = startBin; k < endBin && k < totalBins; k++) {
                sum += dataArray[k];
                count++;
            }
            let val = count > 0 ? sum / count : 0;

            // Fallback: if range was too narrow (startBin >= endBin or count=0), sample the startBin directly
            if (count === 0 && startBin < totalBins) {
                val = dataArray[startBin];
            }

            // Pink noise compensation (boost highs)
            val *= 1 + p * 1.8;
            if (val > peakVal) peakVal = val;

            // Mirror to left/right
            const leftIdx = center - 1 - i;
            const rightIdx = center + i;

            // Smooth with asymmetric rise/fall
            const rise = 0.25;
            const fall = 0.08; // Slower fall for smoother decay

            for (const idx of [leftIdx, rightIdx]) {
                const prev = this.prevData[idx];
                const target = val;
                this.prevData[idx] = prev + (target - prev) * (target > prev ? rise : fall);
            }
        }

        // Auto-gain with more headroom
        this.maxVol = Math.max(this.maxVol * this.volDecay, peakVal, 40);
        const normFactor = 200 / this.maxVol;

        // Normalize and apply contrast curve
        for (let c = 0; c < this.gridCols; c++) {
            let v = (this.prevData[c] * normFactor) / 255;

            // Noise gate: important to scale the bars
            const gate = 0.5;
            if (v < gate) v = 0;
            else v = (v - gate) / (1 - gate);

            // Soft compression + contrast
            v = Math.pow(Math.min(1, v), 2.2);
            result[c] = v;
        }

        return result;
    }

    // Draw rounded capsule shape
    drawCapsule(ctx, cx, cy, w, h) {
        if (h < w) {
            ctx.beginPath();
            ctx.arc(cx, cy, Math.max(0.5, h / 2), 0, Math.PI * 2);
            ctx.fill();
            return;
        }
        const halfH = h / 2;
        const r = w / 2;
        ctx.beginPath();
        ctx.arc(cx, cy - halfH + r, r, Math.PI, 0);
        ctx.lineTo(cx + r, cy + halfH - r);
        ctx.arc(cx, cy + halfH - r, r, 0, Math.PI);
        ctx.lineTo(cx - r, cy - halfH + r);
        ctx.closePath();
        ctx.fill();
    }

    // Adjust hex color brightness
    adjustBrightness(hex, factor) {
        const r = parseInt(hex.slice(1, 3), 16);
        const g = parseInt(hex.slice(3, 5), 16);
        const b = parseInt(hex.slice(5, 7), 16);
        const clamp = (v) => Math.min(255, Math.max(0, Math.round(v * factor)));
        return `rgb(${clamp(r)},${clamp(g)},${clamp(b)})`;
    }

    destroy() {
        if (this.glCanvas) {
            this.glCanvas.remove();
            this.glCanvas = null;
        }
        if (this.gl) {
            const ext = this.gl.getExtension('WEBGL_lose_context');
            if (ext) ext.loseContext();
            this.gl = null;
        }
        this.glInitialized = false;
        this.glProgram = null;
    }
}
