// Harness dla www/page-carousel.js: uruchamia prawdziwą karuzelę w jsdom.
//
// Sprawdza przede wszystkim podmiankę strony w miejscu (wiadomość z `replaces`):
// pozycja w karuzeli i żywa strona nie mogą się od niej ruszyć, a hash musi dalej
// jednoznacznie wskazywać stronę.
//
// Wypisuje na stdout JSON: {"results": [{"id", "name", "ok", "detail"}, ...]}
// Konsumuje go test_carousel.py — nie zmieniaj formatu bez poprawienia testu.

const fs = require('fs');
const path = require('path');
const { JSDOM } = require('jsdom');

const WWW = path.resolve(__dirname, '..', '..', 'www');
const source = fs.readFileSync(path.join(WWW, 'page-carousel.js'), 'utf8');

const tick = () => new Promise(r => setTimeout(r, 0));

function makeCarousel() {
    const dom = new JSDOM(
        `<!DOCTYPE html><body><div class="container"><main id="mainContent"></main></div></body>`,
        { runScripts: 'outside-only', pretendToBeVisual: true },
    );
    const { window } = dom;
    window.eval(source);          // rejestruje window.createPageCarousel

    const content = window.document.getElementById('mainContent');
    const renders = [];           // hashe stron, które trafiły na żywy slajd
    const live = [];              // hashe zgłoszone przez onLiveChange

    const carousel = window.createPageCarousel({
        content,
        render: (page) => {
            renders.push(page.hash);
            content.innerHTML = page.html ?? '';
        },
    });
    carousel.onLiveChange((hash) => live.push(hash));

    const track = window.document.querySelector('.carousel-track');

    // afterSettle czeka na transitionend toru albo na bezpiecznik 450 ms. W teście
    // domykamy przejazd ręcznie, żeby nie czekać na zegar — i kilka razy, bo drain()
    // może po zakończeniu jednego przejazdu od razu ruszyć następny.
    async function settle() {
        for (let i = 0; i < 6; i++) {
            const event = new window.Event('transitionend');
            event.propertyName = 'transform';
            track.dispatchEvent(event);
            await tick();
        }
    }

    return {
        window,
        carousel,
        content,
        renders,
        live,
        settle,
        // treść jest dowolna, liczy się hash: to on jest identyfikatorem strony
        push: async (hash, replaces = null) => {
            carousel.push({ html: `<div>${hash}</div>`, js: '', hash, replaces });
            await settle();
        },
        hashes: () => {
            // karuzela nie wystawia listy stron, więc czytamy to, co widać:
            // pozycję, długość i hash żywej strony
            return { index: carousel.index, length: carousel.length, current: carousel.currentHash };
        },
    };
}

// ---------------------------------------------------------------- przypadki

const results = [];

function check(id, name, ok, detail = '') {
    results.push({ id, name, ok: !!ok, detail: String(detail) });
}

async function scenarioPushesAndDedup() {
    const c = makeCarousel();

    await c.push('A');
    check('push_first_page_lives', 'pierwsza strona od razu jest żywa',
          c.carousel.index === 0 && c.carousel.currentHash === 'A', JSON.stringify(c.hashes()));

    await c.push('B');
    check('push_second_page_navigates', 'druga strona wchodzi i karuzela na nią przejeżdża',
          c.carousel.index === 1 && c.carousel.length === 2 && c.carousel.currentHash === 'B',
          JSON.stringify(c.hashes()));

    await c.push('A');
    check('push_known_hash_scrolls_back', 'znany hash przewija do istniejącej strony, nie dubluje',
          c.carousel.length === 2 && c.carousel.currentHash === 'A', JSON.stringify(c.hashes()));
}

async function scenarioReplaceLivePage() {
    const c = makeCarousel();

    await c.push('A');
    await c.push('B');

    const rendersBefore = c.renders.length;
    await c.push('B2', 'B');

    check('replace_live_keeps_position', 'podmianka żywej strony nie rusza pozycji ani liczby stron',
          c.carousel.index === 1 && c.carousel.length === 2, JSON.stringify(c.hashes()));
    check('replace_live_swaps_hash', 'żywa strona ma od teraz nowy hash',
          c.carousel.currentHash === 'B2', `current=${c.carousel.currentHash}`);
    check('replace_live_rerenders', 'nowa treść trafia na ekran',
          c.renders.length === rendersBefore + 1 && c.renders.at(-1) === 'B2',
          `renders=${JSON.stringify(c.renders)}`);
    check('replace_live_notifies', 'zmiana strony przed użytkownikiem jest zgłoszona',
          c.live.at(-1) === 'B2', `live=${JSON.stringify(c.live)}`);
}

async function scenarioReplaceNeighbourPage() {
    const c = makeCarousel();

    await c.push('A');
    await c.push('B');

    const rendersBefore = c.renders.length;
    const liveBefore = c.live.length;
    await c.push('A2', 'A');

    check('replace_neighbour_keeps_live', 'podmianka sąsiada nie zmienia żywej strony',
          c.carousel.index === 1 && c.carousel.currentHash === 'B', JSON.stringify(c.hashes()));
    check('replace_neighbour_no_rerender', 'podmianka sąsiada nie renderuje na żywym slajdzie',
          c.renders.length === rendersBefore, `renders=${JSON.stringify(c.renders)}`);
    check('replace_neighbour_no_notify', 'podmianka sąsiada nie zgłasza zmiany strony',
          c.live.length === liveBefore, `live=${JSON.stringify(c.live)}`);

    // po podmiance stary hash nie istnieje, a nowy wskazuje tę samą pozycję
    c.carousel.prev();
    await c.settle();
    check('replace_neighbour_new_hash_usable', 'przewinięcie wstecz pokazuje podmienioną stronę',
          c.carousel.index === 0 && c.carousel.currentHash === 'A2', JSON.stringify(c.hashes()));
}

async function scenarioReplaceUnknownFallsBack() {
    const c = makeCarousel();

    await c.push('A');
    await c.push('X', 'nie-ma-takiej');

    check('replace_unknown_becomes_new_page', 'brak strony do podmiany nie gubi treści',
          c.carousel.length === 2 && c.carousel.currentHash === 'X', JSON.stringify(c.hashes()));
}

async function scenarioReplaceCollapsesTwin() {
    const c = makeCarousel();

    await c.push('A');
    await c.push('B');

    // podmianka A na treść identyczną z B: dwie strony o tym samym hashu to jeden
    // ekran pokazany dwa razy, więc karuzela musi je zwinąć
    await c.push('B', 'A');

    check('replace_twin_collapses', 'dwie strony o tym samym hashu zwijają się w jedną',
          c.carousel.length === 1, JSON.stringify(c.hashes()));
    check('replace_twin_keeps_live_hash', 'po zwinięciu żywa strona ma nadal ten hash',
          c.carousel.currentHash === 'B' && c.carousel.index === 0, JSON.stringify(c.hashes()));

    // karuzela musi dalej działać: kolejna strona wchodzi normalnie
    await c.push('C');
    check('replace_twin_leaves_usable_carousel', 'po zwinięciu karuzela dalej przyjmuje strony',
          c.carousel.length === 2 && c.carousel.index === 1 && c.carousel.currentHash === 'C',
          JSON.stringify(c.hashes()));
}

(async () => {
    try {
        await scenarioPushesAndDedup();
        await scenarioReplaceLivePage();
        await scenarioReplaceNeighbourPage();
        await scenarioReplaceUnknownFallsBack();
        await scenarioReplaceCollapsesTwin();
    } catch (error) {
        console.log(JSON.stringify({ error: error.message, stack: error.stack, results }));
        process.exit(2);
    }

    console.log(JSON.stringify({ results }));
    process.exit(results.every(r => r.ok) ? 0 : 1);
})();
