/**
 * Analogue gauge tuner visualization for the FeedBack tuner plugin.
 *
 * Contract: window['_tunerViz_analogue-gauge'](container) → { update(note, cents, freq), destroy() }
 *   - note: string | null  (null = no signal)
 *   - cents: number        (deviation from target, −50…+50)
 *   - freq: number         (detected frequency in Hz)
 *
 * Layout (vintage analogue instrument panel):
 *   - Off-white panel face
 *   - Full-width black gauge section; frequency drum window centred inside it
 *   - Red SVG needle sweeps over the freq drum window
 *   - Note name drum + lightbulb below the gauge
 */
(function () {
    'use strict';

    // ── Constants ─────────────────────────────────────────────────────
    var _TUNER_LABEL_H = 12;           // px height of each drum label
    var _TUNER_NEEDLE_HALF_SWEEP = 90; // degrees — ±50 cents = horizontal (180° apart)
    var _SETTLE_A = 0.05;              // deg — needle "settled" threshold (sub-visible)
    var _SETTLE_Y = 0.1;               // px — drum-strip "settled" threshold
    var _TUNER_IN_TUNE_THRESHOLD = 2;
    var _TUNER_STRIP_START_MIDI = 14;  // ~18 Hz — covers 20 Hz minimum
    var _TUNER_STRIP_END_MIDI = 84;    // ~1047 Hz C6
    var _TUNER_NOTE_NAMES = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'];
    var _TUNER_NOTE_FLAT  = ['C','Db','D','Eb', 'E','F','Gb','G','Ab','A','Bb', 'B'];
    // SVG gauge geometry — viewBox 200 × 110, pivot at bottom-centre
    // R=95 keeps arc endpoints ~5 SVG units from the viewBox edges to prevent clipping
    var _SVG_CX = 100, _SVG_CY = 110, _SVG_R = 95, _SVG_NEEDLE_LEN = 88;

    window['_tunerViz_analogue-gauge'] = function (container) {
        'use strict';

        var svgNS = 'http://www.w3.org/2000/svg';

        function _midiToFreq(m) { return Math.pow(2, (m - 69) / 12) * 440; }

        // ── Panel (off-white, vintage) ────────────────────────────────
        var panel = document.createElement('div');
        panel.className = 'w-full relative flex flex-col items-center gap-2 p-3 rounded-lg';
        panel.style.backgroundColor = '#e8e0cc';
        panel.style.border = '2px solid #b0a080';

        // ── AUTO lamp (top-left; lit in free-tune mode) ──────────────
        var autoWrap = document.createElement('div');
        autoWrap.style.position = 'absolute';
        autoWrap.style.top = '8px';
        autoWrap.style.left = '10px';
        autoWrap.style.zIndex = '20';
        autoWrap.style.display = 'flex';
        autoWrap.style.alignItems = 'center';
        autoWrap.style.gap = '4px';

        var autoLamp = document.createElement('div');
        autoLamp.style.width = '8px';
        autoLamp.style.height = '8px';
        autoLamp.style.backgroundColor = '#2a0000';
        autoLamp.style.border = '1px solid #5a2020';
        autoLamp.style.flexShrink = '0';

        var autoLabel = document.createElement('span');
        autoLabel.style.fontSize = '9px';
        autoLabel.style.fontFamily = 'monospace';
        autoLabel.style.fontWeight = 'bold';
        autoLabel.style.color = '#888';
        autoLabel.textContent = 'AUTO';

        autoWrap.appendChild(autoLamp);
        autoWrap.appendChild(autoLabel);
        panel.appendChild(autoWrap);

        // ── A=440 label (top-right) ───────────────────────────────────
        var refLabel = document.createElement('span');
        refLabel.style.position = 'absolute';
        refLabel.style.top = '8px';
        refLabel.style.right = '10px';
        refLabel.style.zIndex = '20';
        refLabel.style.fontSize = '9px';
        refLabel.style.fontFamily = 'monospace';
        refLabel.style.fontWeight = 'bold';
        refLabel.style.color = '#888';
        refLabel.textContent = 'A=440';
        panel.appendChild(refLabel);

        // ── Gauge section (full-width, black face) ────────────────────
        var gaugeFace = document.createElement('div');
        gaugeFace.className = 'w-full relative';
        gaugeFace.style.backgroundColor = '#e8e0cc';
        gaugeFace.style.height = '95px'; // matches cropped viewBox height (110-15)

        // Frequency drum window — centred inside the gauge, behind the needle
        var freqWindow = document.createElement('div');
        freqWindow.style.position = 'absolute';
        freqWindow.style.overflow = 'hidden';
        freqWindow.style.backgroundColor = '#fff';
        freqWindow.style.border = '1px solid #bbb';
        freqWindow.style.width = '104px';
        freqWindow.style.height = (_TUNER_LABEL_H * 2) + 'px';
        freqWindow.style.left = 'calc(50% - 52px)';
        freqWindow.style.top = '39px'; // half needle from pivot: 95 - 88/2 - 24/2 = 39
        freqWindow.style.zIndex = '1';
        // Inset shadows top & bottom → suggests a curved drum surface receding at the edges.
        // Top is shorter/lighter, bottom taller/darker — reads as a drum lit from above.
        freqWindow.style.boxShadow = 'inset 0 4px 5px -4px rgba(0,0,0,0.35), inset 0 -7px 7px -4px rgba(0,0,0,0.55)';

        var freqStrip = document.createElement('div');
        freqStrip.style.position = 'absolute';
        freqStrip.style.width = '100%';

        // "---" is index 0; actual notes start at index 1
        function _makeDrumLabel(text) {
            var el = document.createElement('div');
            el.style.height = _TUNER_LABEL_H + 'px';
            el.style.display = 'flex';
            el.style.alignItems = 'center';
            el.style.justifyContent = 'center';
            el.style.userSelect = 'none';
            el.textContent = text;
            return el;
        }
        var fIdleLabel = _makeDrumLabel('---');
        fIdleLabel.style.fontSize = '11px';
        fIdleLabel.style.fontFamily = 'monospace';
        fIdleLabel.style.fontWeight = 'bold';
        fIdleLabel.style.color = '#111';
        freqStrip.appendChild(fIdleLabel);
        freqStrip.appendChild(_makeDrumLabel('')); // separator: keeps real labels out of view at idle

        for (var fm = _TUNER_STRIP_START_MIDI; fm <= _TUNER_STRIP_END_MIDI; fm++) {
            var fLabel = _makeDrumLabel(_midiToFreq(fm).toFixed(1) + ' Hz');
            fLabel.style.fontSize = '11px';
            fLabel.style.fontFamily = 'monospace';
            fLabel.style.fontWeight = 'bold';
            fLabel.style.color = '#111';
            freqStrip.appendChild(fLabel);
        }
        freqWindow.appendChild(freqStrip);
        gaugeFace.appendChild(freqWindow);

        // SVG — arc, tick marks, needle, pivot (z above freq window)
        var svg = document.createElementNS(svgNS, 'svg');
        svg.setAttribute('viewBox', '0 15 200 95'); // crop 15px dead space above arc top
        svg.setAttribute('preserveAspectRatio', 'none');
        svg.style.position = 'absolute';
        svg.style.top = '0';
        svg.style.left = '0';
        svg.style.width = '100%';
        svg.style.height = '100%';
        svg.style.zIndex = '2';
        svg.style.overflow = 'visible'; // prevent viewBox from clipping arc edges

        // Arc: R=95 keeps endpoints ~5 SVG units from the viewBox edges
        var arcPath = document.createElementNS(svgNS, 'path');
        arcPath.setAttribute('d', 'M 5 110 A 95 95 0 0 1 195 110');
        arcPath.setAttribute('fill', 'none');
        arcPath.setAttribute('stroke', '#222');
        arcPath.setAttribute('stroke-width', '1.5');
        svg.appendChild(arcPath);

        // Tick marks: long every 10 cents, 4 short between each (every 2 cents).
        // 5 outermost marks on each side (|c| >= 42) in red.
        for (var tc = -50; tc <= 50; tc += 2) {
            var isLong  = (tc % 10 === 0);
            var isRed   = Math.abs(tc) >= 42;
            var tLen    = isLong ? 10 : 5;
            var tColor  = isRed ? '#cc2200' : '#222';
            var tWidth  = isLong ? 1.5 : 1;
            var tAngleRad = ((tc / 50) * _TUNER_NEEDLE_HALF_SWEEP - 90) * Math.PI / 180;
            var ttick = document.createElementNS(svgNS, 'line');
            ttick.setAttribute('x1', (_SVG_CX + (_SVG_R - tLen) * Math.cos(tAngleRad)).toFixed(1));
            ttick.setAttribute('y1', (_SVG_CY + (_SVG_R - tLen) * Math.sin(tAngleRad)).toFixed(1));
            ttick.setAttribute('x2', (_SVG_CX + _SVG_R * Math.cos(tAngleRad)).toFixed(1));
            ttick.setAttribute('y2', (_SVG_CY + _SVG_R * Math.sin(tAngleRad)).toFixed(1));
            ttick.setAttribute('stroke', tColor);
            ttick.setAttribute('stroke-width', String(tWidth));
            svg.appendChild(ttick);
        }

        // Inner labels — dominant-baseline="central" so y = vertical centre of text
        [
            { c: -50, text: '-50', extreme: true },
            { c: -30, text: '-30', yOff: -1 },
            { c:   0, text:  '0',  yOff: -1 },
            { c:  30, text: '+30', yOff: -1 },
            { c:  50, text: '+50', extreme: true }
        ].forEach(function (m) {
            var aRad = ((m.c / 50) * _TUNER_NEEDLE_HALF_SWEEP - 90) * Math.PI / 180;
            var lx = (_SVG_CX + 76 * Math.cos(aRad)).toFixed(1);
            // extreme labels: centre at arc baseline (y=_SVG_CY)
            // others: on arc circle at r=76 plus per-label vertical nudge
            var ly = m.extreme
                ? String(_SVG_CY)
                : (_SVG_CY + 76 * Math.sin(aRad) + (m.yOff || 0)).toFixed(1);
            var lbl = document.createElementNS(svgNS, 'text');
            lbl.setAttribute('x', lx);
            lbl.setAttribute('y', ly);
            lbl.setAttribute('text-anchor', 'middle');
            lbl.setAttribute('dominant-baseline', 'central');
            lbl.setAttribute('font-size', '8');
            lbl.setAttribute('font-family', 'monospace');
            lbl.setAttribute('fill', m.extreme ? '#cc2200' : '#555');
            lbl.textContent = m.text;
            svg.appendChild(lbl);
        });

        // Needle line (pivot at SVG bottom-centre; x2/y2 updated in RAF)
        var needleLine = document.createElementNS(svgNS, 'line');
        needleLine.setAttribute('x1', '100');
        needleLine.setAttribute('y1', '110');
        needleLine.setAttribute('x2', '100');
        needleLine.setAttribute('y2', String(110 - _SVG_NEEDLE_LEN)); // initial: 0 cents
        needleLine.setAttribute('stroke', '#cc2200');
        needleLine.setAttribute('stroke-width', '2');
        needleLine.setAttribute('stroke-linecap', 'round');
        svg.appendChild(needleLine);

        // Pivot cap
        var pivotCap = document.createElementNS(svgNS, 'circle');
        pivotCap.setAttribute('cx', '100');
        pivotCap.setAttribute('cy', '110');
        pivotCap.setAttribute('r', '5');
        pivotCap.setAttribute('fill', '#cc2200');
        svg.appendChild(pivotCap);

        gaugeFace.appendChild(svg);
        panel.appendChild(gaugeFace);

        // ── Note drum + lightbulb row (below gauge) ───────────────────
        var noteRow = document.createElement('div');
        noteRow.className = 'w-full relative flex justify-center items-center';

        var noteWindow = document.createElement('div');
        noteWindow.style.position = 'relative';
        noteWindow.style.overflow = 'hidden';
        noteWindow.style.backgroundColor = '#fff';
        noteWindow.style.border = '1px solid #999';
        noteWindow.style.width = '48px';
        noteWindow.style.height = (_TUNER_LABEL_H * 2) + 'px';
        // Inset shadows top & bottom → suggests a curved drum surface receding at the edges.
        // Top is shorter/lighter, bottom taller/darker — reads as a drum lit from above.
        noteWindow.style.boxShadow = 'inset 0 4px 5px -4px rgba(0,0,0,0.35), inset 0 -7px 7px -4px rgba(0,0,0,0.55)';

        var noteStrip = document.createElement('div');
        noteStrip.style.position = 'absolute';
        noteStrip.style.width = '100%';

        var nIdleLabel = _makeDrumLabel('---');
        nIdleLabel.style.fontSize = '10px';
        nIdleLabel.style.fontWeight = 'bold';
        nIdleLabel.style.color = '#111';
        noteStrip.appendChild(nIdleLabel);
        noteStrip.appendChild(_makeDrumLabel('')); // separator

        var _drumLabels = []; // {el, nm} — for flat/sharp relabeling
        for (var nm = _TUNER_STRIP_START_MIDI; nm <= _TUNER_STRIP_END_MIDI; nm++) {
            var nLabel = _makeDrumLabel(_TUNER_NOTE_NAMES[nm % 12]);
            nLabel.style.fontSize = '10px';
            nLabel.style.fontWeight = 'bold';
            nLabel.style.color = '#111';
            noteStrip.appendChild(nLabel);
            _drumLabels.push({ el: nLabel, nm: nm });
        }
        noteWindow.appendChild(noteStrip);

        // Lightbulb — absolutely offset from panel centre so note window stays centred
        // noteWindow is 48px wide → bulb left edge = 50% + 24px (half window) + 6px gap
        var bulbEl = document.createElement('div');
        bulbEl.style.position = 'absolute';
        bulbEl.style.left = 'calc(50% + 30px)';
        bulbEl.style.top = '50%';
        bulbEl.style.transform = 'translateY(-50%)';
        bulbEl.style.width = '20px';
        bulbEl.style.height = '20px';
        bulbEl.style.borderRadius = '50%';
        bulbEl.style.backgroundColor = '#2a1010';
        bulbEl.style.border = '2px solid #4a2020';
        noteRow.appendChild(noteWindow);
        noteRow.appendChild(bulbEl);

        panel.appendChild(noteRow);
        container.appendChild(panel);

        // ── State ─────────────────────────────────────────────────────
        // Must be defined before currentDrumY initialisation (var hoisting trap)
        var _IDLE_DRUM_Y = _TUNER_LABEL_H * 0.5; // centres --- label (index 0) in window
        var currentDrumY = _IDLE_DRUM_Y, targetDrumY = _IDLE_DRUM_Y;
        var _lastUseFlats = false;
        var currentAngle = 0, targetAngle = 0;
        var lastTime = performance.now();
        var rafId = null;

        // ── Needle SVG update ─────────────────────────────────────────
        function _setNeedle(angleDeg) {
            var rad = (angleDeg - 90) * Math.PI / 180;
            needleLine.setAttribute('x2', (_SVG_CX + _SVG_NEEDLE_LEN * Math.cos(rad)).toFixed(1));
            needleLine.setAttribute('y2', (_SVG_CY + _SVG_NEEDLE_LEN * Math.sin(rad)).toFixed(1));
        }

        // ── Drum position ─────────────────────────────────────────────

        function _computeDrumY(freq, cents) {
            if (!freq || freq <= 0) return _IDLE_DRUM_Y;
            var midi = 69 + 12 * Math.log2(freq / 440);
            var targetMidi = midi - cents / 100;
            var clamped = Math.max(-50, Math.min(50, cents));
            // +2: index 0 = ---, index 1 = separator, index 2+ = real notes
            var idx = Math.max(2, Math.min(_TUNER_STRIP_END_MIDI - _TUNER_STRIP_START_MIDI + 2, Math.round(targetMidi) - _TUNER_STRIP_START_MIDI + 2));
            return _TUNER_LABEL_H * (0.5 - idx) - (clamped / 50) * (_TUNER_LABEL_H / 2);
        }

        // ── Animation loop ────────────────────────────────────────────
        function _animate() {
            var now = performance.now();
            var dt = Math.min((now - lastTime) / 1000, 0.1);
            lastTime = now;
            var lf = 1 - Math.exp(-10 * dt);

            currentDrumY += (targetDrumY - currentDrumY) * lf;
            freqStrip.style.transform = 'translateY(' + currentDrumY + 'px)';
            noteStrip.style.transform = 'translateY(' + currentDrumY + 'px)';

            currentAngle += (targetAngle - currentAngle) * lf;
            _setNeedle(currentAngle);

            // Stop once the needle has settled on its target — a static needle
            // needs no repaint. update() re-kicks the loop when a new reading
            // moves the target, so this idles the always-on tuner (no signal /
            // steady pitch) instead of pinning a core at 60 fps forever.
            if (Math.abs(targetDrumY - currentDrumY) <= _SETTLE_Y
                    && Math.abs(targetAngle - currentAngle) <= _SETTLE_A) {
                currentDrumY = targetDrumY; currentAngle = targetAngle;
                freqStrip.style.transform = 'translateY(' + currentDrumY + 'px)';
                noteStrip.style.transform = 'translateY(' + currentDrumY + 'px)';
                _setNeedle(currentAngle);
                rafId = null;
                return;
            }
            rafId = requestAnimationFrame(_animate);
        }

        // Restart the loop only when there's actually something to animate toward
        // (a new target). Reset lastTime so the first frame after an idle gap
        // doesn't take one big easing step.
        function _kick() {
            if (rafId === null
                    && (Math.abs(targetDrumY - currentDrumY) > _SETTLE_Y
                        || Math.abs(targetAngle - currentAngle) > _SETTLE_A)) {
                lastTime = performance.now();
                rafId = requestAnimationFrame(_animate);
            }
        }

        rafId = requestAnimationFrame(_animate);

        // ── Public API ────────────────────────────────────────────────
        function _setAutoLamp(lit) {
            autoLamp.style.backgroundColor = lit ? '#cc2200' : '#2a0000';
            autoLamp.style.border = lit ? '1px solid #ff4422' : '1px solid #5a2020';
            autoLamp.style.boxShadow = lit ? '0 0 5px 2px rgba(200,50,0,0.7)' : 'none';
        }

        function update(note, cents, freq, mode, targetFreq, referencePitch, useFlats) {
            if (typeof referencePitch === 'number' && referencePitch > 0) {
                refLabel.textContent = 'A=' + Math.round(referencePitch);
            }
            var wantFlats = !!useFlats;
            if (wantFlats !== _lastUseFlats) {
                _lastUseFlats = wantFlats;
                var names = wantFlats ? _TUNER_NOTE_FLAT : _TUNER_NOTE_NAMES;
                for (var _di = 0; _di < _drumLabels.length; _di++) {
                    _drumLabels[_di].el.textContent = names[_drumLabels[_di].nm % 12];
                }
            }
            // AUTO lamp: free → always lit; auto → lit on signal; manual/unknown → off
            if (mode === 'free') {
                _setAutoLamp(true);
            } else if (mode === 'auto') {
                _setAutoLamp(note !== null);
            } else {
                _setAutoLamp(false);
            }

            if (note === null) {
                targetDrumY = _IDLE_DRUM_Y;
                targetAngle = 0;
                bulbEl.style.backgroundColor = '#2a1010';
                bulbEl.style.border = '2px solid #4a2020';
                bulbEl.style.boxShadow = 'none';
                _kick();   // animate back to rest, then the loop self-stops
                return;
            }

            targetDrumY = _computeDrumY(freq, cents);

            targetAngle = (Math.max(-50, Math.min(50, cents)) / 50) * _TUNER_NEEDLE_HALF_SWEEP;

            if (Math.abs(cents) <= _TUNER_IN_TUNE_THRESHOLD) {
                bulbEl.style.backgroundColor = '#cc3300';
                bulbEl.style.border = '2px solid #ff5522';
                bulbEl.style.boxShadow = '0 0 10px 4px rgba(200,50,0,0.85)';
            } else {
                bulbEl.style.backgroundColor = '#2a1010';
                bulbEl.style.border = '2px solid #4a2020';
                bulbEl.style.boxShadow = 'none';
            }
            _kick();   // a new reading moved the target → run until it settles
        }

        function destroy() {
            if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
            panel.remove();
        }

        return { update: update, destroy: destroy };
    };
})();
