'use strict';

const { test } = require('node:test');
const assert = require('node:assert/strict');
const { _yinDetect } = require('../../../../plugins/tuner/workers/yin.js');

const SAMPLE_RATE = 44100;
const FRAME = 4096;

function sine(freq, nSamples, amplitude = 0.8) {
    const buf = new Float32Array(nSamples);
    for (let i = 0; i < nSamples; i++) {
        buf[i] = amplitude * Math.sin(2 * Math.PI * freq * i / SAMPLE_RATE);
    }
    return buf;
}

function assertFreq(result, expected, tolerancePct = 1.5, label = '') {
    assert.ok(result.freq > 0, `${label}: expected a detected frequency, got 0`);
    const errorPct = Math.abs(result.freq - expected) / expected * 100;
    assert.ok(
        errorPct < tolerancePct,
        `${label}: expected ${expected} Hz ±${tolerancePct}%, got ${result.freq.toFixed(2)} Hz (error ${errorPct.toFixed(2)}%)`
    );
}

// ── Silence / no-signal ──────────────────────────────────────────────────────

test('silence (all zeros) returns freq 0', () => {
    const result = _yinDetect(new Float32Array(FRAME), SAMPLE_RATE);
    assert.equal(result.freq, 0);
    assert.equal(result.confidence, 0);
});

test('sub-threshold amplitude returns freq 0', () => {
    // rms < 0.01 must be rejected before any pitch estimation
    const buf = sine(440, FRAME, 0.001);
    const result = _yinDetect(buf, SAMPLE_RATE);
    assert.equal(result.freq, 0);
});

// ── Clean sine pitches — guitar range ────────────────────────────────────────

test('detects E2 (82.41 Hz) — low E string', () => {
    assertFreq(_yinDetect(sine(82.41, FRAME), SAMPLE_RATE), 82.41, 1.5, 'E2');
});

test('detects A2 (110 Hz) — A string', () => {
    assertFreq(_yinDetect(sine(110, FRAME), SAMPLE_RATE), 110, 1.5, 'A2');
});

test('detects D3 (146.83 Hz) — D string', () => {
    assertFreq(_yinDetect(sine(146.83, FRAME), SAMPLE_RATE), 146.83, 1.5, 'D3');
});

test('detects G3 (196 Hz) — G string', () => {
    assertFreq(_yinDetect(sine(196, FRAME), SAMPLE_RATE), 196, 1.5, 'G3');
});

test('detects B3 (246.94 Hz) — B string', () => {
    assertFreq(_yinDetect(sine(246.94, FRAME), SAMPLE_RATE), 246.94, 1.5, 'B3');
});

test('detects E4 (329.63 Hz) — high E string', () => {
    assertFreq(_yinDetect(sine(329.63, FRAME), SAMPLE_RATE), 329.63, 1.5, 'E4');
});

test('detects A4 (440 Hz) — concert A', () => {
    assertFreq(_yinDetect(sine(440, FRAME), SAMPLE_RATE), 440, 1.5, 'A4');
});

// ── Confidence ────────────────────────────────────────────────────────────────

test('high confidence for clean sine at A4', () => {
    const result = _yinDetect(sine(440, FRAME), SAMPLE_RATE);
    assert.ok(result.confidence > 0.8, `Expected confidence > 0.8, got ${result.confidence.toFixed(3)}`);
});

test('rms reflects signal level', () => {
    const quiet = _yinDetect(sine(440, FRAME, 0.1), SAMPLE_RATE);
    const loud  = _yinDetect(sine(440, FRAME, 0.9), SAMPLE_RATE);
    assert.ok(loud.rms > quiet.rms, 'louder signal should have higher rms');
});

// ── Edge cases ────────────────────────────────────────────────────────────────

test('minimum buffer size (4096 samples) does not throw', () => {
    assert.doesNotThrow(() => _yinDetect(sine(440, 4096), SAMPLE_RATE));
});

test('returns object with freq, confidence, rms keys', () => {
    const result = _yinDetect(sine(440, FRAME), SAMPLE_RATE);
    assert.ok('freq' in result, 'missing freq');
    assert.ok('confidence' in result, 'missing confidence');
    assert.ok('rms' in result, 'missing rms');
});
