// Behavioural tests for the per-note bend-curve (bnv, §6.2.1) render helpers:
// `bnvNormalizedPoints` (static/highway.js, 2D glyph) and `bnvSampleAt`
// (plugins/highway_3d/screen.js, 3D Y gesture). Both are pure, so we extract
// the function source by brace-matching and eval it in isolation.

const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');

function extractFn(src, name) {
    const start = src.indexOf('function ' + name);
    assert.ok(start >= 0, `function ${name} must exist`);
    const open = src.indexOf('{', start);
    let depth = 0;
    for (let i = open; i < src.length; i++) {
        if (src[i] === '{') depth++;
        else if (src[i] === '}' && --depth === 0) return src.slice(start, i + 1);
    }
    throw new Error(`unbalanced braces extracting ${name}`);
}

function loadFn(file, name) {
    const src = fs.readFileSync(path.join(__dirname, '..', '..', file), 'utf8');
    return new Function('"use strict";' + extractFn(src, name) + `\nreturn ${name};`)();
}

const bnvNormalizedPoints = loadFn('static/highway.js', 'bnvNormalizedPoints');
const bnvSampleAt = loadFn('plugins/highway_3d/screen.js', 'bnvSampleAt');

// ── bnvNormalizedPoints (2D) ─────────────────────────────────────────────────

test('bnvNormalizedPoints normalizes t to 0..1 across the curve span (no sus)', () => {
    const pts = bnvNormalizedPoints([
        { t: 0.5, v: 0 }, { t: 1.0, v: 2 }, { t: 1.5, v: 0 }]);
    assert.deepEqual(pts, [
        { x: 0, v: 0 }, { x: 0.5, v: 2 }, { x: 1, v: 0 }]);
});

test('bnvNormalizedPoints maps t over the note sus span when given', () => {
    // A bend that completes at t=0.4 of a 0.5s note draws to x=0.8, not x=1 —
    // i.e. it stops short of the glyph's right edge (correct timing shape).
    assert.deepEqual(
        bnvNormalizedPoints([{ t: 0, v: 0 }, { t: 0.25, v: 1 }, { t: 0.4, v: 0 }], 0.5),
        [{ x: 0, v: 0 }, { x: 0.5, v: 1 }, { x: 0.8, v: 0 }]);
    // Points beyond sus clamp to 1; sus<=0 falls back to curve-span mapping.
    assert.deepEqual(bnvNormalizedPoints([{ t: 0, v: 0 }, { t: 1, v: 2 }], 0.5),
        [{ x: 0, v: 0 }, { x: 1, v: 2 }]);
    assert.deepEqual(bnvNormalizedPoints([{ t: 0, v: 0 }, { t: 1, v: 2 }], 0),
        [{ x: 0, v: 0 }, { x: 1, v: 2 }]);
});

test('bnvNormalizedPoints handles degenerate/empty input', () => {
    assert.deepEqual(bnvNormalizedPoints([]), []);
    assert.deepEqual(bnvNormalizedPoints(null), []);
    // All-same-t span collapses x to 0 (no divide-by-zero).
    assert.deepEqual(bnvNormalizedPoints([{ t: 1, v: 1 }, { t: 1, v: 2 }]),
        [{ x: 0, v: 1 }, { x: 0, v: 2 }]);
});

// ── bnvSampleAt (3D) ─────────────────────────────────────────────────────────

test('bnvSampleAt linearly interpolates between points', () => {
    const bnv = [{ t: 0, v: 0 }, { t: 1, v: 2 }];
    assert.equal(bnvSampleAt(bnv, 0.5), 1);   // midpoint
    assert.equal(bnvSampleAt(bnv, 0.25), 0.5);
});

test('bnvSampleAt clamps to the endpoints', () => {
    const bnv = [{ t: 0.2, v: 1 }, { t: 0.8, v: 3 }];
    assert.equal(bnvSampleAt(bnv, 0), 1);     // before first
    assert.equal(bnvSampleAt(bnv, 5), 3);     // after last
});

test('bnvSampleAt traces a round-trip curve up then back down', () => {
    const bnv = [{ t: 0, v: 0 }, { t: 0.5, v: 2 }, { t: 1, v: 0 }];
    assert.equal(bnvSampleAt(bnv, 0.25), 1);  // rising
    assert.equal(bnvSampleAt(bnv, 0.5), 2);   // peak
    assert.equal(bnvSampleAt(bnv, 0.75), 1);  // falling
});

test('bnvSampleAt returns 0 for an empty/invalid curve', () => {
    assert.equal(bnvSampleAt([], 0.5), 0);
    assert.equal(bnvSampleAt(null, 0.5), 0);
});

test('bnvSampleAt tolerates a zero-width segment (duplicate t)', () => {
    const bnv = [{ t: 0, v: 0 }, { t: 0.5, v: 1 }, { t: 0.5, v: 2 }, { t: 1, v: 2 }];
    assert.equal(bnvSampleAt(bnv, 0.5), 1);   // first matching segment wins
});
