// ============================================================
// Background: WebGL shader-based dynamic background
// ============================================================

import { frequencyData } from './player.js';

let bgCanvas = null;
let bgGL = null;
let bgProgram = null;
let bgUniforms = null;       // Cached uniform locations
let bgAnimFrame = null;
let bgEnabled = localStorage.getItem('dynamicBackground') === 'true';
let bgStartTime = 0;
let bgSmoothedBands = new Float32Array(16);
let bgSmoothedBass = 0;
let bgAccentRGB = [0.114, 0.725, 0.329]; // Cached accent color (default green)
let bgBgColorRGB = [0.071, 0.071, 0.071]; // Cached page background (#121212)

const BG_BAND_COUNT = 16;
const BG_SMOOTHING = 0.12;

// ---- Shaders ----

const BG_VERT_SRC = `
attribute vec2 a_position;
void main() {
    gl_Position = vec4(a_position, 0.0, 1.0);
}
`;

const BG_FRAG_SRC = `
precision mediump float;

uniform vec2 u_resolution;
uniform float u_time;
uniform float u_bass;
uniform float u_bands[16];
uniform vec3 u_accent;
uniform vec3 u_bgColor;

// Smooth noise
float hash(vec2 p) {
    return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}

float noise(vec2 p) {
    vec2 i = floor(p);
    vec2 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    float a = hash(i);
    float b = hash(i + vec2(1.0, 0.0));
    float c = hash(i + vec2(0.0, 1.0));
    float d = hash(i + vec2(1.0, 1.0));
    return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

void main() {
    vec2 uv = gl_FragCoord.xy / u_resolution;
    float aspect = u_resolution.x / u_resolution.y;

    // Center coordinates for radial effects
    vec2 center = (uv - 0.5) * vec2(aspect, 1.0);
    float dist = length(center);
    float angle = atan(center.y, center.x);

    // Slow base animation
    float t = u_time * 0.15;

    // === Layer 1: Flowing wave field ===
    float waves = 0.0;
    for (int i = 0; i < 5; i++) {
        float fi = float(i);
        float freq = 1.5 + fi * 0.8;
        float speed = t * (0.6 + fi * 0.15);
        // Sample a band for this wave layer
        int bandIdx = i * 3;
        float bandVal = 0.0;
        // Manual indexing (GLSL ES doesn't allow variable array index in some drivers)
        for (int j = 0; j < 16; j++) {
            if (j == bandIdx) bandVal = u_bands[j];
        }
        float amp = 0.015 + bandVal * 0.06;
        waves += amp * sin(uv.x * freq * 6.2832 + speed + sin(uv.y * 3.0 + t) * 2.0);
        waves += amp * 0.5 * sin(uv.y * freq * 4.0 - speed * 0.7 + cos(uv.x * 2.5 + t) * 1.5);
    }

    // === Layer 2: Radial pulse (bass-driven) ===
    float pulse = smoothstep(0.6 + u_bass * 0.3, 0.0, dist) * (0.08 + u_bass * 0.15);

    // === Layer 3: Frequency ring arcs ===
    float rings = 0.0;
    for (int i = 0; i < 8; i++) {
        float fi = float(i);
        float bandVal = 0.0;
        for (int j = 0; j < 16; j++) {
            if (j == i * 2) bandVal = u_bands[j];
        }
        float radius = 0.15 + fi * 0.1;
        float ringWidth = 0.008 + bandVal * 0.025;
        float ring = smoothstep(ringWidth, 0.0, abs(dist - radius - bandVal * 0.05));
        // Fade ring by angle sector for variety
        float angleFade = 0.5 + 0.5 * sin(angle * (2.0 + fi) + t * (1.0 + fi * 0.3));
        rings += ring * angleFade * (0.3 + bandVal * 0.7);
    }

    // === Layer 4: Subtle noise texture ===
    float n = noise(uv * 4.0 + t * 0.5) * 0.03;

    // Combine layers
    float intensity = waves + pulse + rings * 0.5 + n;

    // Color: accent color with varying brightness
    vec3 col = u_accent * intensity;

    // Subtle secondary hue shift for depth
    vec3 shifted = u_accent.gbr;  // Rotated accent
    col += shifted * rings * 0.15;

    // Vignette
    float vignette = 1.0 - smoothstep(0.3, 1.2, dist);
    col *= vignette;

    // Blend over page background
    col = clamp(col, 0.0, 1.0);
    float colBright = (col.r + col.g + col.b) / 3.0;
    float bgLum = dot(u_bgColor, vec3(0.299, 0.587, 0.114));
    // Dark bg: add accent light.  Light bg: tint white toward accent via multiply.
    vec3 darkResult  = u_bgColor + col;
    vec3 lightResult = u_bgColor * mix(vec3(1.0), u_accent, colBright * 2.0);
    vec3 finalColor = clamp(mix(darkResult, lightResult, bgLum), 0.0, 1.0);

    gl_FragColor = vec4(finalColor, 1.0);
}
`;

// ---- WebGL setup ----

function initBackgroundGL() {
    bgCanvas = document.getElementById('bg-shader-canvas');
    if (!bgCanvas) return false;

    bgGL = bgCanvas.getContext('webgl', { alpha: false, antialias: false, depth: false, stencil: false });
    if (!bgGL) {
        console.warn('WebGL not available for background shader');
        return false;
    }

    const gl = bgGL;

    // Compile shaders
    const vs = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vs, BG_VERT_SRC);
    gl.compileShader(vs);
    if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
        console.error('BG vertex shader:', gl.getShaderInfoLog(vs));
        return false;
    }

    const fs = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fs, BG_FRAG_SRC);
    gl.compileShader(fs);
    if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
        console.error('BG fragment shader:', gl.getShaderInfoLog(fs));
        return false;
    }

    bgProgram = gl.createProgram();
    gl.attachShader(bgProgram, vs);
    gl.attachShader(bgProgram, fs);
    gl.linkProgram(bgProgram);
    if (!gl.getProgramParameter(bgProgram, gl.LINK_STATUS)) {
        console.error('BG program link:', gl.getProgramInfoLog(bgProgram));
        return false;
    }

    // Fullscreen quad
    const buf = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buf);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        -1, -1,  1, -1,  -1, 1,
        -1,  1,  1, -1,   1, 1
    ]), gl.STATIC_DRAW);

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

    gl.useProgram(bgProgram);

    // Cache uniform locations once (avoids per-frame lookups)
    bgUniforms = {
        resolution: gl.getUniformLocation(bgProgram, 'u_resolution'),
        time:       gl.getUniformLocation(bgProgram, 'u_time'),
        bass:       gl.getUniformLocation(bgProgram, 'u_bass'),
        bands:      gl.getUniformLocation(bgProgram, 'u_bands'),
        accent:     gl.getUniformLocation(bgProgram, 'u_accent'),
        bgColor:    gl.getUniformLocation(bgProgram, 'u_bgColor'),
    };

    bgStartTime = performance.now() / 1000;
    updateBackgroundColors();
    resizeBackgroundCanvas();
    window.addEventListener('resize', resizeBackgroundCanvas);

    return true;
}

function resizeBackgroundCanvas() {
    if (!bgCanvas) return;
    const dpr = Math.min(window.devicePixelRatio || 1, 1.5); // Cap DPR for performance
    const w = Math.floor(window.innerWidth * dpr);
    const h = Math.floor(window.innerHeight * dpr);
    if (bgCanvas.width !== w || bgCanvas.height !== h) {
        bgCanvas.width = w;
        bgCanvas.height = h;
    }
}

// ---- Cached color/theme updates (called on accent or theme change, not per-frame) ----

export function updateBackgroundColors() {
    const style = getComputedStyle(document.documentElement);
    const accentHex = style.getPropertyValue('--accent').trim();
    if (accentHex && accentHex.length >= 7) {
        bgAccentRGB[0] = parseInt(accentHex.slice(1, 3), 16) / 255;
        bgAccentRGB[1] = parseInt(accentHex.slice(3, 5), 16) / 255;
        bgAccentRGB[2] = parseInt(accentHex.slice(5, 7), 16) / 255;
    }
    const bgHex = style.getPropertyValue('--bg-primary').trim();
    if (bgHex && bgHex.length >= 7) {
        bgBgColorRGB[0] = parseInt(bgHex.slice(1, 3), 16) / 255;
        bgBgColorRGB[1] = parseInt(bgHex.slice(3, 5), 16) / 255;
        bgBgColorRGB[2] = parseInt(bgHex.slice(5, 7), 16) / 255;
    }
}

// ---- Render loop ----

// Cached step into the bins array; recomputed only when bins.length
// changes (which happens at most once after the first audio frame
// arrives or when num_bins is reconfigured).
let bgBinsLength = -1;
let bgBinsStep = 1;
// Last applied resolution — drawing with stale viewport is harmless,
// but we still need to refresh the uniform after the resize listener
// has updated the canvas.
let bgLastResW = -1;
let bgLastResH = -1;

function renderBackgroundFrame() {
    bgAnimFrame = requestAnimationFrame(renderBackgroundFrame);

    const gl = bgGL;
    if (!gl || !bgUniforms) return;

    // Resize listener already keeps canvas dimensions in sync — only
    // touch the viewport when the canvas actually changed size, so the
    // per-frame path doesn't read window.innerWidth (a layout-flushing
    // property).
    if (bgCanvas.width !== bgLastResW || bgCanvas.height !== bgLastResH) {
        bgLastResW = bgCanvas.width;
        bgLastResH = bgCanvas.height;
        gl.viewport(0, 0, bgLastResW, bgLastResH);
        gl.uniform2f(bgUniforms.resolution, bgLastResW, bgLastResH);
    }

    const time = performance.now() / 1000 - bgStartTime;

    // Smooth audio data from the imported frequencyData (shared with visualizer).
    // Backend may send float bins (legacy) or int×1000 (new); .scale tells us which.
    if (frequencyData && frequencyData.frequencies) {
        const bins = frequencyData.frequencies;
        const scale = frequencyData.scale && frequencyData.scale > 0
            ? 1.0 / frequencyData.scale : 1.0;
        if (bins.length !== bgBinsLength) {
            bgBinsLength = bins.length;
            bgBinsStep = Math.max(1, Math.floor(bgBinsLength / BG_BAND_COUNT));
        }
        const step = bgBinsStep;
        for (let i = 0; i < BG_BAND_COUNT; i++) {
            let idx = i * step;
            if (idx >= bgBinsLength) idx = bgBinsLength - 1;
            const target = (bins[idx] || 0) * scale;
            bgSmoothedBands[i] += (target - bgSmoothedBands[i]) * (1 - BG_SMOOTHING);
        }
        const targetBass = (frequencyData.bass || 0) * scale;
        bgSmoothedBass += (targetBass - bgSmoothedBass) * (1 - BG_SMOOTHING);
    } else {
        // Gentle decay when no audio
        for (let i = 0; i < BG_BAND_COUNT; i++) {
            bgSmoothedBands[i] *= 0.95;
        }
        bgSmoothedBass *= 0.95;
    }

    // Set uniforms (locations cached at init, colors cached on change)
    gl.uniform1f(bgUniforms.time, time);
    gl.uniform1f(bgUniforms.bass, bgSmoothedBass);
    gl.uniform1fv(bgUniforms.bands, bgSmoothedBands);
    gl.uniform3f(bgUniforms.accent, bgAccentRGB[0], bgAccentRGB[1], bgAccentRGB[2]);
    gl.uniform3f(bgUniforms.bgColor, bgBgColorRGB[0], bgBgColorRGB[1], bgBgColorRGB[2]);

    gl.drawArrays(gl.TRIANGLES, 0, 6);
}

function startBackground() {
    if (bgAnimFrame) return;
    if (!bgGL && !initBackgroundGL()) return;
    bgCanvas.classList.add('visible');
    document.body.classList.add('dynamic-bg-active');
    renderBackgroundFrame();
}

function stopBackground() {
    if (bgAnimFrame) {
        cancelAnimationFrame(bgAnimFrame);
        bgAnimFrame = null;
    }
    if (bgCanvas) {
        bgCanvas.classList.remove('visible');
    }
    document.body.classList.remove('dynamic-bg-active');
}

// ---- Public API ----

export function toggleDynamicBackground() {
    bgEnabled = !bgEnabled;
    localStorage.setItem('dynamicBackground', bgEnabled);
    applyDynamicBackground();
}

export function applyDynamicBackground() {
    const btn = document.getElementById('bgToggle');
    if (bgEnabled) {
        startBackground();
        if (btn) btn.classList.add('active');
    } else {
        stopBackground();
        if (btn) btn.classList.remove('active');
    }
}
