(function () {
    'use strict';
    function freqToMidi(f) { return 69 + 12 * Math.log2(f / 440); }
    function midiToFreq(m) { return Math.pow(2, (m - 69) / 12) * 440; }

    const _NOTE_NAMES = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'];
    const _NOTE_FLAT  = ['C','Db','D','Eb', 'E','F','Gb','G','Ab','A','Bb', 'B'];
    // Returns true when the tuning name implies flat notation (e.g. "Eb Standard", "Bb Standard").
    function preferFlats(tuningName) {
        return typeof tuningName === 'string' && /\b[A-G]b\b/.test(tuningName);
    }
    function midiToNote(m, useFlats) {
        return (useFlats ? _NOTE_FLAT : _NOTE_NAMES)[((Math.round(m) % 12) + 12) % 12];
    }


    // Per-string-count standard open-string MIDI arrays
    const _BASE_MIDI = {
        4: [28, 33, 38, 43],                  // bass 4: E1 A1 D2 G2
        5: [23, 28, 33, 38, 43],              // bass 5: B0 E1 A1 D2 G2
        6: [40, 45, 50, 55, 59, 64],          // guitar 6: E2 A2 D3 G3 B3 E4
        7: [35, 40, 45, 50, 55, 59, 64],      // guitar 7: B1 E2 A2 D3 G3 B3 E4
        8: [30, 35, 40, 45, 50, 55, 59, 64],  // guitar 8: F#1 B1 E2 A2 D3 G3 B3 E4
    };

    function offsetsToFreqs(offsets, isBass) {
        const len = offsets.length;
        let base;
        if (len === 4 || len === 5) {
            base = isBass ? _BASE_MIDI[len] : _BASE_MIDI[6];
        } else {
            base = _BASE_MIDI[len] || _BASE_MIDI[6];
        }
        return offsets.map((offset, i) => {
            const root = i < base.length ? base[i] : base[base.length - 1];
            return midiToFreq(root + offset);
        });
    }

    function getTuningName(offsets) {
        if (!offsets || offsets.length === 0) return 'Unknown';
        const len = offsets.length;
        if (len < 4 || len > 8) return offsets.join(' ');

        // First-string open MIDI for this length (same table as _BASE_MIDI column 0).
        const firstStringMidi = (_BASE_MIDI[len] || _BASE_MIDI[6])[0];
        const noteNames = ['C','C#','D','Eb','E','F','F#','G','Ab','A','A#','B'];

        // All-equal: name by the note the lowest string becomes at this offset.
        if (offsets.every(o => o === offsets[0])) {
            const noteIdx = ((firstStringMidi + offsets[0]) % 12 + 12) % 12;
            return noteNames[noteIdx] + ' Standard';
        }

        // Drop tuning: first string is exactly 2 semitones below the rest (all equal).
        if (offsets[0] === offsets[1] - 2 && offsets.slice(1).every(o => o === offsets[1])) {
            const noteIdx = ((firstStringMidi + offsets[0]) % 12 + 12) % 12;
            return 'Drop ' + noteNames[noteIdx];
        }

        // Named lookup table for specific patterns
        const named = {
            // 6-string
            '-2,0,0,0,0,0': 'Drop D', '-4,-2,-2,-2,-2,-2': 'Drop C',
            '-2,-2,0,0,0,0': 'Double Drop D', '0,0,0,-1,0,0': 'Open G',
            '-2,-2,0,0,-2,-2': 'Open D', '-2,0,0,0,-2,0': 'DADGAD',
            '0,2,2,1,0,0': 'Open E', '-2,0,0,2,3,2': 'Open D (alt)',
            // 7-string
            '-2,0,0,0,0,0,0': 'Drop A',
            // 8-string
            '-2,0,0,0,0,0,0,0': 'Drop E',
        };
        const key = offsets.join(',');
        if (named[key]) return named[key];

        return offsets.join(' ');
    }

    window._tunerUtils = { freqToMidi, midiToFreq, midiToNote, offsetsToFreqs, getTuningName, preferFlats };
})();
