// ── NoteSpec.js ───────────────────────────────────────────────────────────────
//
// Unit specs for note.js (the slide-down Brief banner). The banner module is
// exposed as `Brief` (with `Note` kept as an alias during the C3 sprint).
//
// DOM contract assumed by showBanner():
//   Any <h2> in the document — banner is inserted immediately after it.
//   If absent, banner is prepended to <body>.
//
// API under test:
//   Brief.showBanner(brief)
//     brief = { id, kind, title, line_text, post_url, square_url, created_at }
//                                                      → inject .note-banner
//     brief = null                                     → no-op
//
//   Brief.handleSaveResponse(data)
//     data = { saved: true, brief: {...}     }  → delegates to showBanner
//     data = { saved: true, brief: null      }  → no-op
//
// ─────────────────────────────────────────────────────────────────────────────

const SAMPLE_BRIEF = {
    id:         '00000000-0000-0000-0000-000000000001',
    kind:       'note_unlock',
    title:      'Stargazer',
    line_text:  'Stargazer, 02:00:00 AM',
    post_url:   '/billboard/post/abc/',
    square_url: '/billboard/my-notes/',
    created_at: '2026-04-22T02:00:00+00:00',
};

describe('Brief.showBanner', () => {

    let fixture;

    beforeEach(() => {
        fixture = document.createElement('div');
        fixture.id = 'note-fixture';
        fixture.innerHTML = '<h2>Dash</h2>';
        document.body.appendChild(fixture);
    });

    afterEach(() => {
        document.querySelectorAll('.note-banner').forEach(b => b.remove());
        fixture.remove();
    });

    // ── T1 ── null → no banner ────────────────────────────────────────────────

    it('T1: showBanner(null) does not inject a banner', () => {
        Brief.showBanner(null);
        expect(document.querySelector('.note-banner')).toBeNull();
    });

    // ── T2 ── brief present → banner in DOM ──────────────────────────────────

    it('T2: showBanner(brief) injects .note-banner into the document', () => {
        Brief.showBanner(SAMPLE_BRIEF);
        expect(document.querySelector('.note-banner')).not.toBeNull();
    });

    // ── T3 ── title ───────────────────────────────────────────────────────────

    it('T3: banner .note-banner__title contains brief.title', () => {
        Brief.showBanner(SAMPLE_BRIEF);
        const el = document.querySelector('.note-banner__title');
        expect(el).not.toBeNull();
        expect(el.textContent).toContain('Stargazer');
    });

    // ── T4 ── description (line_text) ─────────────────────────────────────────

    it('T4: banner .note-banner__description carries brief.line_text', () => {
        Brief.showBanner(SAMPLE_BRIEF);
        const el = document.querySelector('.note-banner__description');
        expect(el).not.toBeNull();
        expect(el.textContent).toContain('Stargazer, 02:00:00 AM');
    });

    // ── T5 ── timestamp ───────────────────────────────────────────────────────

    it('T5: banner has a .note-banner__timestamp element', () => {
        Brief.showBanner(SAMPLE_BRIEF);
        expect(document.querySelector('.note-banner__timestamp')).not.toBeNull();
    });

    // ── T6 ── image area; for note_unlock kind it's a clickable square ──────

    it('T6: banner .note-banner__image is a clickable <a> when brief.square_url is set', () => {
        Brief.showBanner(SAMPLE_BRIEF);
        const sq = document.querySelector('.note-banner__image');
        expect(sq).not.toBeNull();
        expect(sq.tagName.toLowerCase()).toBe('a');
        expect(sq.getAttribute('href')).toBe('/billboard/my-notes/');
    });

    // ── T7 ── NVM button ──────────────────────────────────────────────────────

    it('T7: banner has a .btn.btn-cancel NVM button', () => {
        Brief.showBanner(SAMPLE_BRIEF);
        expect(document.querySelector('.note-banner .btn.btn-cancel')).not.toBeNull();
    });

    // ── T8 ── FYI link points to brief.post_url (not hardcoded my-notes) ─────

    it('T8: banner FYI .btn.btn-info link points to brief.post_url', () => {
        Brief.showBanner(SAMPLE_BRIEF);
        const fyi = document.querySelector('.note-banner .btn.btn-info');
        expect(fyi).not.toBeNull();
        expect(fyi.getAttribute('href')).toBe('/billboard/post/abc/');
    });

    // ── T9 ── NVM dismissal ───────────────────────────────────────────────────

    it('T9: clicking the NVM button removes the banner from the DOM', () => {
        Brief.showBanner(SAMPLE_BRIEF);
        document.querySelector('.note-banner .btn.btn-cancel').click();
        expect(document.querySelector('.note-banner')).toBeNull();
    });

    // ── T10 ── placement: anchor preferred over h2 ───────────────────────────

    it('T10a: banner is inserted as nextSibling of #id_brief_banner_anchor when present', () => {
        const anchor = document.createElement('div');
        anchor.id = 'id_brief_banner_anchor';
        fixture.appendChild(anchor);
        Brief.showBanner(SAMPLE_BRIEF);
        expect(anchor.nextElementSibling.classList.contains('note-banner')).toBeTrue();
    });

    it('T10b: falls back to inserting after the first h2 when anchor is absent', () => {
        Brief.showBanner(SAMPLE_BRIEF);
        const h2 = fixture.querySelector('h2');
        expect(h2.nextElementSibling.classList.contains('note-banner')).toBeTrue();
    });

});


// ─────────────────────────────────────────────────────────────────────────────
//
// Brief.showDuplicateBanner — the "already added" error variant. Distinct
// from showBanner because: the server doesn't persist a Brief row for these
// (transient, client-only), title is rendered as `@<username> is already
// present`, there's no date/square/post_url, and the FYI button toggles
// `.bud-duplicate-flash` onto a caller-supplied target element instead of
// navigating.
//
// API under test:
//   Brief.showDuplicateBanner({ display_name, target_selector? })
//     null / missing display_name → no banner
//     target_selector matches an element → FYI click adds .bud-duplicate-flash
//     target_selector missing or matches nothing → FYI just dismisses
//
// ─────────────────────────────────────────────────────────────────────────────

describe('Brief.showDuplicateBanner', () => {

    let fixture;

    beforeEach(() => {
        fixture = document.createElement('div');
        fixture.id = 'dup-fixture';
        fixture.innerHTML =
            '<h2>Dash</h2>' +
            '<ul id="bud-fixture-list">' +
                '<li class="bud-entry" data-bud-id="42">' +
                    '<span class="bud-name">alice</span>' +
                '</li>' +
            '</ul>';
        document.body.appendChild(fixture);
    });

    afterEach(() => {
        document.querySelectorAll('.note-banner').forEach(b => b.remove());
        fixture.remove();
    });

    it('D1: missing display_name → no banner', () => {
        Brief.showDuplicateBanner({});
        expect(document.querySelector('.note-banner')).toBeNull();
    });

    it('D2: title reads "@<display_name> is already present"', () => {
        Brief.showDuplicateBanner({ display_name: 'alice' });
        const title = document.querySelector('.note-banner__title');
        expect(title).not.toBeNull();
        expect(title.textContent).toBe('@alice is already present');
    });

    it('D3: banner carries the .note-banner--duplicate modifier class', () => {
        Brief.showDuplicateBanner({ display_name: 'alice' });
        const b = document.querySelector('.note-banner');
        expect(b).not.toBeNull();
        expect(b.classList.contains('note-banner--duplicate')).toBeTrue();
    });

    it('D4: duplicate banner has no .note-banner__description or .note-banner__timestamp', () => {
        Brief.showDuplicateBanner({ display_name: 'alice' });
        expect(document.querySelector('.note-banner__description')).toBeNull();
        expect(document.querySelector('.note-banner__timestamp')).toBeNull();
    });

    it('D5: FYI button is a <button>, not an <a> (no navigation)', () => {
        Brief.showDuplicateBanner({ display_name: 'alice' });
        const fyi = document.querySelector('.note-banner .note-banner__fyi');
        expect(fyi).not.toBeNull();
        expect(fyi.tagName.toLowerCase()).toBe('button');
    });

    it('D6: NVM dismisses without adding .bud-duplicate-flash anywhere', () => {
        Brief.showDuplicateBanner({
            display_name: 'alice',
            target_selector: '.bud-entry[data-bud-id="42"] .bud-name',
        });
        document.querySelector('.note-banner__nvm').click();
        expect(document.querySelector('.note-banner')).toBeNull();
        const name = fixture.querySelector('.bud-name');
        expect(name.classList.contains('bud-duplicate-flash')).toBeFalse();
    });

    it('D7: FYI dismisses AND adds .bud-duplicate-flash to the target element', () => {
        Brief.showDuplicateBanner({
            display_name: 'alice',
            target_selector: '.bud-entry[data-bud-id="42"] .bud-name',
        });
        document.querySelector('.note-banner__fyi').click();
        expect(document.querySelector('.note-banner')).toBeNull();
        const name = fixture.querySelector('.bud-name');
        expect(name.classList.contains('bud-duplicate-flash')).toBeTrue();
    });

    it('D7b: .bud-duplicate-flash auto-eases out — class is removed ~3s after FYI', () => {
        jasmine.clock().install();
        try {
            Brief.showDuplicateBanner({
                display_name: 'alice',
                target_selector: '.bud-entry[data-bud-id="42"] .bud-name',
            });
            document.querySelector('.note-banner__fyi').click();
            const name = fixture.querySelector('.bud-name');
            // Immediately after FYI: flash is on (the visible peak state).
            expect(name.classList.contains('bud-duplicate-flash')).toBeTrue();
            // After the auto-dismiss window: flash is gone.
            jasmine.clock().tick(3001);
            expect(name.classList.contains('bud-duplicate-flash')).toBeFalse();
        } finally {
            jasmine.clock().uninstall();
        }
    });

    it('D8: FYI dismisses cleanly when target_selector is missing', () => {
        Brief.showDuplicateBanner({ display_name: 'alice' });
        document.querySelector('.note-banner__fyi').click();
        expect(document.querySelector('.note-banner')).toBeNull();
    });

    it('D9: FYI dismisses cleanly when target_selector matches nothing', () => {
        Brief.showDuplicateBanner({
            display_name: 'alice',
            target_selector: '.gate-slot[data-user-id="does-not-exist"]',
        });
        document.querySelector('.note-banner__fyi').click();
        expect(document.querySelector('.note-banner')).toBeNull();
    });

    it('D10: title escapes HTML in display_name', () => {
        Brief.showDuplicateBanner({ display_name: '<script>x</script>' });
        const title = document.querySelector('.note-banner__title');
        expect(title.textContent).toBe('@<script>x</script> is already present');
        expect(title.querySelector('script')).toBeNull();
    });
});

describe('Brief.handleSaveResponse', () => {

    afterEach(() => {
        document.querySelectorAll('.note-banner').forEach(b => b.remove());
    });

    // ── T11 ── delegates when brief present ──────────────────────────────────

    it('T11: handleSaveResponse shows banner when data.brief is present', () => {
        Brief.handleSaveResponse({ saved: true, brief: SAMPLE_BRIEF });
        expect(document.querySelector('.note-banner')).not.toBeNull();
    });

    // ── T12 ── no banner when brief null ─────────────────────────────────────

    it('T12: handleSaveResponse does not show banner when data.brief is null', () => {
        Brief.handleSaveResponse({ saved: true, brief: null });
        expect(document.querySelector('.note-banner')).toBeNull();
    });

});
