/**
 * Testet die Lauf-Anzeige der Bausteinspalte ohne Browser.
 *
 * Die Methoden werden aus studio.js herausgeloest und gegen ein schlankes
 * DOM-Double laufen gelassen. Kein Netzzugriff, keine Kosten.
 *
 * Aufruf aus dem Projektstamm:
 *     node tests/test_lauf.js
 */
'use strict';

const fs = require('fs');
const path = require('path');
const vm = require('vm');

let ok = 0;
let fail = 0;

function pruefe(name, bedingung, extra) {
    if (bedingung) {
        ok++;
        console.log('  OK    ' + name);
    } else {
        fail++;
        console.log('  FEHL  ' + name + ' '
            + (extra === undefined ? '' : JSON.stringify(extra)));
    }
}

// --- DOM-Double ------------------------------------------------------------
function element(id) {
    const klassen = new Set();
    const el = {
        id: id,
        innerHTML: '',
        textContent: '',
        title: '',
        hidden: false,
        disabled: false,
        classList: {
            add: (n) => klassen.add(n),
            remove: (n) => klassen.delete(n),
            toggle: (n, an) => { if (an) klassen.add(n); else klassen.delete(n); },
            contains: (n) => klassen.has(n),
        },
        style: {},
        _kinder: {},
        querySelector(sel) {
            const k = sel.replace('.', '');
            if (!this._kinder[k]) this._kinder[k] = element(k);
            return this._kinder[k];
        },
    };
    return el;
}

const IDS = ['lauf-stand', 'ls-haupt', 'ls-neben', 'ls-btn', 'ls-hinweis',
             'lauf-kette', 'lauf-fortschritt', 'lauf-schritte', 'start-panel',
             'art-summary-meta', 'art-fc-meta', 'art-snap-meta', 'art-tl-meta',
             'studio-cols', 'studio-empty'];
const elemente = {};
const meldungen = [];
const aufrufe = [];

const umgebung = {
    document: {
        getElementById: (id) => elemente[id] || null,
        querySelectorAll: () => [],
    },
    UI: {
        showToast: (t, a) => meldungen.push({ t, a }),
        escape: (s) => String(s === undefined || s === null ? '' : s)
            .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'),
    },
    CSS: { escape: (s) => s },
    localStorage: { _d: {}, setItem(k, v) { this._d[k] = v; }, getItem(k) { return this._d[k]; } },
    console: console,
};
umgebung.window = umgebung;

// --- Den Lauf-Block aus studio.js herausloesen -----------------------------
const quelle = fs.readFileSync(
    path.join(__dirname, '..', 'src', 'static', 'js', 'studio.js'), 'utf8');
const von = quelle.indexOf('    // === Der Lauf (Spalte 3) ===');
const bis = quelle.indexOf('    async runStage(stage) {', von);
if (von === -1 || bis === -1) {
    console.log('FEHLER: Lauf-Block in studio.js nicht gefunden');
    process.exit(1);
}

const kontext = vm.createContext(umgebung);
const Studio = vm.runInContext(
    'var Studio = {\n' + quelle.slice(von, bis) + '\n'
    + '  incident: null, fresh: null, _runningStage: null,\n'
    + '  async runStage(s) { this._letzterStart = s; },\n'
    // _showEmpty steht ausserhalb des herausgeloesten Blocks, deshalb hier
    // wortgleich nachgebildet.
    + '  _showEmpty(on) {\n'
    + '    const c = document.getElementById("studio-cols");\n'
    + '    if (c) c.classList.toggle("idle", on);\n'
    + '    document.getElementById("studio-empty").style.display = on ? "" : "none";\n'
    + '    if (on) { this.incident = null; this.fresh = null; this._renderLauf(); }\n'
    + '  },\n'
    + '};\nStudio;', kontext);

const LAGE = { id: 7, title: 'Ceuta', type: 'adhoc' };

// Vier Zustaende, wie sie im Betrieb wirklich vorkommen
const FRISCH = {
    leer:     { articles: 0,  summary: {}, factcheck: {}, geoparse: {} },
    veraltet: { articles: 85, summary: { exists: true, last: '2026-08-02 18:10', pending: 3 },
                factcheck: { exists: true, last: '2026-08-02 16:00', facts: 19, pending: 3 },
                geoparse: { pending: 0 }, snapshots: 4, events: 12 },
    aktuell:  { articles: 85, summary: { exists: true, last: '2026-08-02 18:40', pending: 0 },
                factcheck: { exists: true, last: '2026-08-02 18:41', facts: 19, pending: 0 },
                geoparse: { pending: 0 }, snapshots: 4, events: 12 },
    halb:     { articles: 85, summary: { exists: false }, factcheck: { exists: false },
                geoparse: { pending: 85 } },
};

function neu(zustand, ueber) {
    for (const id of IDS) elemente[id] = element(id);
    Studio.incident = Object.assign({}, LAGE, ueber || {});
    Studio.fresh = FRISCH[zustand] ? JSON.parse(JSON.stringify(FRISCH[zustand])) : null;
    Studio._runningStage = null;
    Studio._schrittOffen = null;
    Studio._letzterStart = null;
    meldungen.length = 0;
    aufrufe.length = 0;
}

const g = (id) => elemente[id];
const knopfText = () => g('ls-btn').querySelector('.ls-text').textContent;

async function main() {
    console.log('\nA) Der Startknopf richtet sich nach dem Zustand');
    neu('leer');
    Studio._renderLauf();
    pruefe('A1 leerer Fall lädt zum Start ein', knopfText() === 'Lauf starten', knopfText());
    pruefe('A2 der Stand sagt warum', g('ls-haupt').textContent.includes('Noch keine Artikel'),
           g('ls-haupt').textContent);
    pruefe('A3 der Kasten ist hervorgehoben', g('lauf-stand').classList.contains('betont'));
    pruefe('A4 mit einem Hinweis, was danach kommt',
           g('ls-hinweis').textContent.length > 10 && g('ls-hinweis').hidden === false);

    neu('leer', { type: 'research' });
    Studio._renderLauf();
    pruefe('A5 bei einer Recherche heißt es Recherche starten',
           knopfText() === 'Recherche starten', knopfText());

    neu('veraltet');
    Studio._renderLauf();
    pruefe('A6 bei neuen Artikeln heißt es aktualisieren',
           knopfText() === 'Jetzt aktualisieren', knopfText());
    pruefe('A7 der Stand nennt die Zahl',
           g('ls-haupt').textContent.includes('3 neue Artikel'), g('ls-haupt').textContent);
    pruefe('A8 und warnt', g('ls-haupt').classList.contains('warn'));

    neu('aktuell');
    Studio._renderLauf();
    pruefe('A9 wenn alles frisch ist heißt es erneut durchlaufen',
           knopfText() === 'Erneut durchlaufen', knopfText());
    pruefe('A10 der Kasten ist dann ruhig', !g('lauf-stand').classList.contains('betont'));
    pruefe('A11 und der Hinweis verschwindet', g('ls-hinweis').hidden === true);
    pruefe('A12 der Bestand steht daneben',
           g('ls-neben').textContent === '85 Artikel im Bestand', g('ls-neben').textContent);

    console.log('\nB) Während eines Laufs');
    neu('veraltet');
    Studio._runningStage = 'full';
    Studio._renderLauf();
    pruefe('B1 der Knopf bricht ab', knopfText() === 'Abbrechen', knopfText());
    pruefe('B2 er ist rot abgesetzt', g('ls-btn').classList.contains('abbrechen'));
    pruefe('B3 er bleibt klickbar', g('ls-btn').disabled === false);
    pruefe('B4 der Stand sagt, was läuft',
           g('ls-haupt').textContent.includes('Kompletter Lauf'), g('ls-haupt').textContent);

    neu('veraltet');
    Studio._runningStage = 'analyze';
    Studio._renderLauf();
    pruefe('B5 ein nicht abbrechbarer Schritt sperrt den Knopf', g('ls-btn').disabled === true);
    pruefe('B6 und wird nicht als Abbruch angeboten',
           !g('ls-btn').classList.contains('abbrechen'));
    pruefe('B7 der Stand nennt den Schritt beim Ergebnisnamen',
           g('ls-haupt').textContent.includes('Lagebild schreiben'), g('ls-haupt').textContent);

    console.log('\nC) Der Knopf trifft den richtigen Schritt');
    neu('veraltet');
    await Studio.laufKnopf();
    pruefe('C1 ohne Lauf startet er den kompletten Lauf', Studio._letzterStart === 'full');

    neu('veraltet');
    Studio._runningStage = 'collect';
    await Studio.laufKnopf();
    pruefe('C2 ein laufendes Sammeln wird auch als Sammeln abgebrochen',
           Studio._letzterStart === 'collect', Studio._letzterStart);

    neu('veraltet');
    Studio._runningStage = 'analyze';
    await Studio.laufKnopf();
    pruefe('C3 ein nicht abbrechbarer Schritt löst nichts aus',
           Studio._letzterStart === null, Studio._letzterStart);
    pruefe('C4 und sagt das auch', meldungen.length === 1 && meldungen[0].a === 'info', meldungen);

    console.log('\nD) Die Kette');
    neu('aktuell');
    Studio._renderLauf();
    const kette = g('lauf-kette').innerHTML;
    pruefe('D1 vier Knoten', (kette.match(/class="kn /g) || []).length === 4,
           (kette.match(/class="kn /g) || []).length);
    pruefe('D2 drei Verbindungen', (kette.match(/kn-linie/g) || []).length === 3);
    pruefe('D3 alle vier erledigt', (kette.match(/kn-fertig/g) || []).length === 4);
    pruefe('D4 der Zähler stimmt',
           g('lauf-fortschritt').textContent === '4 von 4 Schritten auf dem neuesten Stand',
           g('lauf-fortschritt').textContent);

    neu('veraltet');
    Studio._renderLauf();
    pruefe('D5 bei veralteten Ergebnissen sind nicht alle grün',
           (g('lauf-kette').innerHTML.match(/kn-fertig/g) || []).length === 2,
           (g('lauf-kette').innerHTML.match(/kn-fertig/g) || []).length);
    pruefe('D6 zwei stehen auf veraltet',
           (g('lauf-kette').innerHTML.match(/kn-veraltet/g) || []).length === 2);
    pruefe('D7 der Zähler zählt nur die fertigen',
           g('lauf-fortschritt').textContent.startsWith('2 von 4'),
           g('lauf-fortschritt').textContent);

    neu('leer');
    Studio._renderLauf();
    pruefe('D8 ohne Artikel sind drei Schritte gesperrt',
           (g('lauf-kette').innerHTML.match(/kn-gesperrt/g) || []).length === 3,
           (g('lauf-kette').innerHTML.match(/kn-gesperrt/g) || []).length);

    console.log('\nE) Die Schritte');
    neu('halb');
    Studio._renderLauf();
    const l = g('lauf-schritte').innerHTML;
    pruefe('E1 vier Schritte', (l.match(/class="schritt /g) || []).length === 4,
           (l.match(/class="schritt /g) || []).length);
    pruefe('E2 Ergebnisnamen statt Erzeugernamen',
           l.includes('Artikel holen') && l.includes('Lagebild schreiben')
           && l.includes('Fakten prüfen') && l.includes('Orte erkennen'));
    pruefe('E3 keine Erzeugernamen mehr',
           !l.includes('Geoparsing') && !l.includes('>Sammeln<') && !l.includes('>Analyse<'));
    pruefe('E4 der erste Schritt ist erledigt', l.includes('schritt schritt-fertig'));
    pruefe('E5 die noch nicht erzeugten sind offen',
           (l.match(/schritt-offen/g) || []).length === 2, (l.match(/schritt-offen/g) || []).length);
    pruefe('E6 die Karte meldet offene Artikel',
           l.includes('85 Artikel noch nicht verortet'));

    neu('aktuell', { type: 'research' });
    Studio._renderLauf();
    pruefe('E7 bei einer Recherche heißt Schritt zwei Recherchebericht',
           g('lauf-schritte').innerHTML.includes('Recherchebericht schreiben'));

    console.log('\nF) Kein Schritt startet etwas');
    for (const z of ['leer', 'veraltet', 'aktuell', 'halb']) {
        neu(z);
        Studio._renderLauf();
        const h = g('lauf-schritte').innerHTML;
        pruefe('F ' + z + ' enthält keinen Startaufruf',
               !h.includes('runStage') && !h.includes('laufKnopf'));
    }

    console.log('\nG) Aufklappen erklärt nur');
    neu('aktuell');
    Studio._renderLauf();
    pruefe('G1 anfangs ist nichts offen',
           !g('lauf-schritte').innerHTML.includes('schritt offen')
           && !g('lauf-schritte').innerHTML.includes(' offen"'));
    Studio.klappSchritt(1);
    pruefe('G2 ein Klick öffnet den Schritt',
           g('lauf-schritte').innerHTML.includes(' offen"'), Studio._schrittOffen);
    pruefe('G3 und zeigt die Erklärung',
           g('lauf-schritte').innerHTML.includes('Wertet alle Artikel aus'));
    Studio.klappSchritt(1);
    pruefe('G4 nochmal klicken schließt ihn', Studio._schrittOffen === null);
    Studio.klappSchritt(0);
    Studio.klappSchritt(2);
    pruefe('G5 es ist immer nur einer offen', Studio._schrittOffen === 2);

    console.log('\nH) Nebenangaben an den Ansichten');
    neu('aktuell');
    Studio._renderLauf();
    pruefe('H1 Lagebild nennt die Artikelzahl',
           g('art-summary-meta').textContent.includes('85 Artikel ausgewertet'),
           g('art-summary-meta').textContent);
    pruefe('H2 Faktencheck nennt die Fakten',
           g('art-fc-meta').textContent.includes('19 Fakten'), g('art-fc-meta').textContent);
    pruefe('H3 frühere Berichte werden gezählt', g('art-snap-meta').textContent === '4');
    pruefe('H4 Ereignisse werden gezählt',
           g('art-tl-meta').textContent === '12 Ereignisse', g('art-tl-meta').textContent);

    neu('leer');
    Studio._renderLauf();
    pruefe('H5 ohne Lauf steht noch nicht erzeugt',
           g('art-summary-meta').textContent === 'noch nicht erzeugt',
           g('art-summary-meta').textContent);

    console.log('\nI) Ohne offenen Fall');
    neu('leer');
    Studio.incident = null;
    Studio.fresh = null;
    Studio._renderLauf();
    pruefe('I1 der Knopf ist gesperrt', g('ls-btn').disabled === true);
    pruefe('I2 der Stand sagt es', g('ls-haupt').textContent === 'Kein Fall geöffnet',
           g('ls-haupt').textContent);
    pruefe('I3 die Kette ist leer', g('lauf-kette').innerHTML === '');
    pruefe('I4 die Schrittliste ist leer', g('lauf-schritte').innerHTML === '');
    pruefe('I5 nichts stürzt ab', true);

    console.log('\nJ) Ohne Frischedaten');
    neu('leer');
    Studio.fresh = null;
    Studio._renderLauf();
    pruefe('J1 die Anzeige bleibt bedienbar', g('ls-btn').disabled === false);
    pruefe('J2 und behandelt den Fall wie leer',
           g('ls-haupt').textContent.includes('Noch keine Artikel'));

    console.log('\nK) Der Leerzustand räumt die Anzeige auf');
    neu('aktuell');
    Studio._renderLauf();
    pruefe('K1 zunächst steht der Stand des Falls da',
           g('ls-haupt').textContent.includes('neuesten Stand'));
    Studio._showEmpty(true);
    pruefe('K2 danach steht dort kein alter Stand mehr',
           g('ls-haupt').textContent === 'Kein Fall geöffnet', g('ls-haupt').textContent);
    pruefe('K3 die Kette ist geleert', g('lauf-kette').innerHTML === '');
    pruefe('K4 die Schrittliste ist geleert', g('lauf-schritte').innerHTML === '');
    pruefe('K5 der Knopf ist gesperrt', g('ls-btn').disabled === true);
    pruefe('K6 der Fall ist wirklich losgelassen', Studio.incident === null);

    console.log('\nErgebnis: ' + ok + ' bestanden, ' + fail + ' fehlgeschlagen');
    process.exit(fail ? 1 : 0);
}

main();
