// Pins the v3 Songs A–Z jump rail wiring in static/v3/songs.js.
//
// The rail lets a user jump the library grid to artists/titles starting with a
// letter (Plex/Radarr/iOS-contacts pattern). With the windowed grid (#636 item 3
// stage 2) the jump seeks DIRECTLY: the sort_letters song-counts give the first
// card's absolute index (cumulative of prior buckets), which converts to a
// scrollTop — no page-through. The rail only offers letters the server reports
// present for the active sort+filter (so a tap always lands on a real card). It
// is shown only for the grid view + alphabetical (artist/title) sorts.
//
// Source-level only — same strategy as tests/js/highway_3d_camera_framing.test.js.

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

const SONGS_JS = path.join(__dirname, '..', '..', 'static', 'v3', 'songs.js');
const src = fs.readFileSync(SONGS_JS, 'utf8');

test('the rail is context-gated to grid view + alphabetical sorts', () => {
    // railSortColumn returns the active alpha column or null (recent/year/tuning).
    assert.match(src, /function\s+railSortColumn\s*\(\)/);
    assert.match(src, /state\.sort === 'artist'[\s\S]*?return 'artist'/);
    assert.match(src, /state\.sort === 'title'[\s\S]*?return 'title'/);
    assert.match(
        src,
        /function\s+railVisible\s*\(\)\s*\{\s*return\s+state\.view === 'grid'\s*&&\s*!!railSortColumn\(\)/,
        'the rail must be visible only for the grid view + an alphabetical sort',
    );
});

test('cards carry a data-letter bucket and non-A–Z buckets under #', () => {
    assert.match(src, /data-letter="'\s*\+\s*esc\(songBucket\(song\)\)/,
        'each card must tag its sort-letter bucket via songBucket(song)');
    assert.match(
        src,
        /function\s+songBucket[\s\S]*?\(ch >= 'A' && ch <= 'Z'\)\s*\?\s*ch\s*:\s*'#'/,
        'songBucket must bucket non-A–Z first chars under "#"',
    );
});

test('refreshRail reads present letters from the stats endpoint (sort-aware)', () => {
    assert.match(src, /\/api\/library\/stats\?'\s*\+\s*queryParams/,
        'refreshRail must query /api/library/stats with the active filter params');
    // Opts into the active-sort breakdown so non-rail callers skip the scan.
    assert.match(src, /queryParams\(\{\s*sort_letters:\s*1\s*\}\)/,
        'refreshRail must request the sort_letters breakdown');
    assert.match(src, /letters\s*=\s*stats\s*&&\s*stats\.sort_letters/,
        'refreshRail must prefer the active-sort breakdown (sort_letters)');
    // The legacy artist `letters` is only a valid fallback for an artist sort;
    // a title sort with no sort_letters hides the rail rather than mislabel it.
    assert.match(src, /col === 'artist'[\s\S]*?stats\.letters/,
        'refreshRail must only fall back to letters for an artist sort');
    // Absent letters are disabled (non-interactive), not just dimmed.
    assert.match(src, /present\s*\?\s*''\s*:\s*' disabled'/);
});

test('reload() refreshes the rail', () => {
    assert.match(src, /function reload\s*\([\s\S]*?refreshRail\(\)/,
        'reload() must call refreshRail() so the rail tracks filter/sort/view changes');
});

test('the rail + drag bubble are rendered in the Songs markup', () => {
    assert.match(src, /id="v3-songs-azrail"[\s\S]*?aria-label="Jump to letter"/);
    assert.match(src, /id="v3-songs-azbubble"/);
});

test('jumpToLetter seeks directly via sort_letters cumulative (no page-through)', () => {
    // The cumulative-count seek: sum the song-counts of buckets ordered before
    // the target to get its first row's absolute index.
    assert.match(src, /function\s+_letterStartIndex\s*\(letter\)/,
        'jumpToLetter must derive the target index from sort_letters counts');
    assert.match(
        src,
        /async function\s+jumpToLetter[\s\S]*?_letterStartIndex\(letter\)[\s\S]*?scrollTo/,
        'jumpToLetter must compute the target index then scrollTo (no _loadNextAwait page-through)',
    );
    // It pre-fetches the destination window so cards are ready when the scroll lands.
    assert.match(src, /async function\s+jumpToLetter[\s\S]*?ensureWindow\(/,
        'jumpToLetter must pre-fetch the destination window before scrolling');
    // The old forward-paging helper is gone (the seek is O(1)).
    assert.doesNotMatch(src, /_loadNextAwait/,
        'the page-through helper must be removed under the windowed grid');
    // A token still guards overlapping jumps (drag scrubbing) — newest wins.
    assert.match(src, /_jumpToken\s*!==\s*myToken/);
});

test('the rail supports pointer drag-scrub + keyboard arrows', () => {
    assert.match(src, /addEventListener\('pointerdown'/);
    assert.match(src, /addEventListener\('pointermove'/);
    assert.match(src, /ArrowUp'[\s\S]*?ArrowDown'|ArrowDown'[\s\S]*?ArrowUp'/,
        'arrow keys must move between present letters');
});
