'use strict';
// Beheer-tab: loc's, wissels, blokken en rijwegen bewerken — zonder JSON.
// Werkt op een lokale kopie van het roster en POST't die naar /api/roster (Opslaan).
(function () {
  const $ = (s) => document.querySelector(s);
  const T = (k) => (window.t ? window.t(k) : k);
  let data = { locos: [], turnouts: [], blocks: [], routes: [], runs: [], consists: [], timetable: [], clockFactor: 8, signals: [], turntables: [], webcamUrl: '', runtime: {}, maintenance: [], shuttles: [], staging: [] };
  function cmd(o) { return fetch('api/cmd', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(o) }); }
  function mkBtn(label, fn) { const b = document.createElement('button'); b.className = 'pwr'; b.style.margin = '3px'; b.textContent = label; b.onclick = fn; return b; }
  function printSchema() {
    // Print-/PDF-vriendelijk baanoverzicht (browser → Afdrukken → Opslaan als PDF)
    const esc = (s) => String(s == null ? '' : s).replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));
    const tbl = (title, head, rows) => !rows.length ? '' : '<h2>' + esc(title) + '</h2><table><thead><tr>' + head.map((h) => '<th>' + esc(h) + '</th>').join('') + '</tr></thead><tbody>' + rows.map((r) => '<tr>' + r.map((c) => '<td>' + esc(c) + '</td>').join('') + '</tr>').join('') + '</tbody></table>';
    let html = '<h1>ModelRailPro — ' + esc(T('export_layout')) + '</h1><p class="muted">' + new Date().toLocaleString() + '</p>';
    html += tbl(T('mng_locos'), [T('mng_addr'), T('mng_name'), T('mng_funcs')], (data.locos || []).map((l) => [l.addr, l.name || '', (l.functions || []).map((f) => f.fn + ':' + f.label).join(' ')]));
    html += tbl(T('mng_turnouts'), [T('mng_id'), T('mng_name'), T('mng_addr')], (data.turnouts || []).map((t) => [t.id, t.name || '', t.addr || '']));
    html += tbl(T('mng_blocks'), [T('mng_id'), T('mng_name'), 'cm'], (data.blocks || []).map((b) => [b.id, b.name || '', b.cm || '']));
    html += tbl(T('mng_routes'), [T('mng_id'), T('mng_name')], (data.routes || []).map((r) => [r.id, r.name || '']));
    html += tbl(T('mng_signals') || 'Seinen', [T('mng_id'), T('mng_name'), T('mng_addr')], (data.signals || []).map((s) => [s.id, s.name || '', s.addr || '']));
    const w = window.open('', '_blank');
    if (!w) { alert(T('export_popup')); return; }
    w.document.write('<!doctype html><html><head><meta charset="utf-8"><title>ModelRailPro baanoverzicht</title><style>body{font-family:system-ui,Arial,sans-serif;margin:24px;color:#111}h1{font-size:22px}h2{font-size:16px;margin-top:22px;border-bottom:2px solid #333;padding-bottom:3px}table{border-collapse:collapse;width:100%;font-size:13px;margin-top:6px}th,td{border:1px solid #bbb;padding:5px 8px;text-align:left}th{background:#eee}.muted{color:#777;font-size:12px}@media print{body{margin:0}}</style></head><body>' + html + '<scr' + 'ipt>window.onload=function(){setTimeout(function(){window.print();},300);};</scr' + 'ipt></body></html>');
    w.document.close();
  }
  function post(url, o) { return fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(o) }); }
  function fillClubOps(c) {
    const ops = document.getElementById('club-ops'); if (!ops) return; ops.innerHTML = '';
    const list = (c && c.operators) || [];
    if (!list.length) { const p = document.createElement('p'); p.className = 'muted small'; p.textContent = T('club_none'); ops.appendChild(p); return; }
    list.forEach((o) => {
      const row = document.createElement('div'); row.className = 'mng-row';
      const nm = document.createElement('span'); nm.className = 'grow'; nm.style.alignSelf = 'center'; nm.textContent = (o.name || o.pid) + (o.role === 'host' ? ' ★' : '');
      const role = document.createElement('select'); role.style.cssText = 'background:#1f242e;color:#e8edf4;border:1px solid #2a313d;border-radius:8px;padding:7px';
      [['host', T('role_host')], ['driver', T('role_driver')], ['guest', T('role_guest')]].forEach(([v, t]) => role.add(new Option(t, v, false, o.role === v)));
      const locs = input((o.locos || []).join(','), T('club_locos')); locs.style.maxWidth = '150px';
      const save = () => post('api/operator/set', { pid: o.pid, role: role.value, locos: locs.value.split(',').map((x) => parseInt(x.trim(), 10)).filter((x) => !isNaN(x)) });
      role.onchange = save; locs.onchange = save;
      row.append(nm, role, locs); ops.appendChild(row);
    });
  }
  let conf = null;
  let integ = null;   // integratie-instellingen (api/integrations)
  const CENTRALS = [
    ['sim', 'Simulator (geen baan)'], ['dccex', 'DCC-EX (WiFi)'], ['z21', 'Roco/Fleischmann Z21 (LAN)'],
    ['dr5000', 'Digikeijs DR5000 (LAN)'], ['ecos', 'ESU ECoS (LAN)'], ['marklin', 'Märklin CS2/CS3 (LAN)'],
    ['twincenter', 'Fleischmann Twin Center (serieel)'], ['xpressnet', 'Lenz/multiMaus (serieel)'], ['loconet', 'Uhlenbrock/LoDi (serieel)'],
  ];
  const SERIAL = ['twincenter', 'xpressnet', 'loconet'];

  // Functietoetsen-presets: vult de functie-lijst van een loc in één keer.
  function presets() {
    const f = (fn, key, mom) => ({ fn, label: T(key), momentary: !!mom });
    return {
      light:    { label: T('preset_light'),    funcs: [f(0, 'fn_light')] },
      diesel:   { label: T('preset_diesel'),   funcs: [f(0, 'fn_light'), f(1, 'fn_sound'), f(2, 'fn_horn', true), f(3, 'fn_shunt'), f(4, 'fn_dynbrake'), f(5, 'fn_bell')] },
      steam:    { label: T('preset_steam'),    funcs: [f(0, 'fn_light'), f(1, 'fn_sound'), f(2, 'fn_whistle', true), f(3, 'fn_shunt'), f(4, 'fn_bell'), f(6, 'fn_smoke')] },
      electric: { label: T('preset_electric'), funcs: [f(0, 'fn_light'), f(1, 'fn_sound'), f(2, 'fn_horn', true), f(3, 'fn_shunt'), f(4, 'fn_announce'), f(5, 'fn_pantograph')] },
    };
  }

  function input(val, ph, cls) {
    const i = document.createElement('input');
    i.value = (val === undefined || val === null) ? '' : val;
    if (ph) i.placeholder = ph;
    if (cls) i.className = cls;
    return i;
  }
  function delBtn(onDel) { const b = document.createElement('button'); b.className = 'mng-del'; b.textContent = '✕'; b.onclick = onDel; return b; }
  function updateDiag() {
    const el = document.getElementById('conn-diag'); if (!el) return;
    const d = window.BB && window.BB.state && window.BB.state().diag; if (!d) { el.textContent = ''; return; }
    const p = [(d.connected ? '🟢 ' + T('diag_connected') : '🔴 ' + T('diag_disconnected')) + ' (' + d.mode + ')'];
    if (d.lastRxAgo != null) p.push(T('diag_lastrx') + ': ' + Math.round(d.lastRxAgo / 1000) + 's');
    if (d.lastError) p.push(T('diag_error') + ': ' + d.lastError);
    const rc = window.BB.state().railcom || []; if (rc.length) p.push('RailCom: ' + rc.join(', '));
    el.textContent = p.join(' · ');
  }

  function section(titleKey, rowsFn, addFn) {
    const sec = document.createElement('div'); sec.className = 'mng-sec';
    const h = document.createElement('h2'); h.textContent = T(titleKey); sec.appendChild(h);
    rowsFn(sec);
    const add = document.createElement('button'); add.className = 'mng-add'; add.textContent = T('mng_add');
    add.onclick = () => { addFn(); render(); };
    sec.appendChild(add);
    return sec;
  }

  function renderConnSection(root) {
    const sec = document.createElement('div'); sec.className = 'mng-sec';
    const h = document.createElement('h2'); h.textContent = T('conn_title'); sec.appendChild(h);
    if (!conf) { const p = document.createElement('p'); p.className = 'muted small'; p.textContent = '…'; sec.appendChild(p); root.appendChild(sec); return; }

    const row1 = document.createElement('div'); row1.className = 'mng-row';
    const lbl = document.createElement('span'); lbl.className = 'muted small'; lbl.textContent = T('conn_central'); lbl.style.minWidth = '90px';
    const modeSel = document.createElement('select'); modeSel.className = 'grow';
    modeSel.style.cssText = 'background:var(--panel2);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:9px';
    CENTRALS.forEach(([v, label]) => { const o = new Option(label, v, false, conf.mode === v); modeSel.add(o); });
    row1.append(lbl, modeSel); sec.appendChild(row1);

    // netwerk: host  /  serieel: poort + baud
    const row2 = document.createElement('div'); row2.className = 'mng-row';
    const host = input(conf.host, T('conn_host'), 'grow');
    const sport = input(conf.serialPort, T('conn_serial'), 'grow');
    const baud = input(conf.baud, 'baud'); baud.type = 'number'; baud.style.maxWidth = '90px';
    function syncFields() {
      const serial = SERIAL.includes(modeSel.value);
      const net = !serial && modeSel.value !== 'sim';
      host.style.display = net ? '' : 'none';
      sport.style.display = serial ? '' : 'none';
      baud.style.display = serial ? '' : 'none';
    }
    modeSel.onchange = syncFields;
    row2.append(host, sport, baud); sec.appendChild(row2); syncFields();

    // verbindingsdiagnose + opnieuw verbinden
    const dg = document.createElement('div'); dg.className = 'mng-row'; dg.style.alignItems = 'center';
    const dstat = document.createElement('span'); dstat.className = 'muted small'; dstat.style.flex = '1'; dstat.id = 'conn-diag';
    const recon = document.createElement('button'); recon.className = 'mng-del'; recon.style.cssText = 'background:var(--panel2);color:var(--accent);border-color:var(--line);width:auto;padding:0 12px'; recon.textContent = '🔌 ' + T('conn_reconnect');
    recon.onclick = () => { if (window.BB) window.BB.send({ type: 'reconnect' }); };
    dg.append(dstat, recon); sec.appendChild(dg); updateDiag();

    // Cloud-koppeling (relay): code om de baan van overal via p33k.nl/baanbrein/app te bedienen
    const row3 = document.createElement('div'); row3.className = 'mng-row';
    const rlbl = document.createElement('span'); rlbl.className = 'muted small'; rlbl.textContent = T('conn_relay'); rlbl.style.minWidth = '90px';
    const relay = input(conf.relayCode, T('conn_relay_ph'), 'grow');
    const gen = document.createElement('button'); gen.className = 'mng-del'; gen.style.cssText = 'background:var(--panel2);color:var(--accent);border-color:var(--line)'; gen.textContent = T('conn_relay_gen');
    gen.onclick = () => { const a = 'abcdefghjkmnpqrstuvwxyz23456789'; let c = ''; for (let i = 0; i < 10; i++) c += a[Math.floor(Math.random() * a.length)]; relay.value = c; };
    row3.append(rlbl, relay, gen); sec.appendChild(row3);
    const rhint = document.createElement('p'); rhint.className = 'muted small';
    rhint.innerHTML = conf.relayCode
      ? (T('conn_relay_on') + ' <a href="https://p33k.nl/baanbrein/app" target="_blank" rel="noopener">p33k.nl/baanbrein/app</a>')
      : T('conn_relay_off');
    sec.appendChild(rhint);

    const save = document.createElement('button'); save.className = 'mng-save'; save.style.marginTop = '4px'; save.textContent = T('conn_save');
    const msg = document.createElement('div'); msg.className = 'mng-saved';
    save.onclick = async () => {
      const rc = relay.value.trim();
      if (rc && rc.length < 6) { msg.className = 'mng-saved'; msg.style.color = 'var(--neg)'; msg.textContent = '✗ ' + T('conn_relay_short'); return; }
      msg.style.color = '';
      const payload = { mode: modeSel.value, host: host.value.trim(), serialPort: sport.value.trim(), baud: parseInt(baud.value, 10) || 19200, relayCode: rc };
      try {
        const r = await (await fetch('api/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) })).json();
        if (!r.ok) { msg.textContent = '✗'; return; }
        conf.relayCode = rc;
        msg.innerHTML = '✓ ' + (r.restartNeeded ? T('conn_saved_restart') : (rc ? T('conn_relay_live') : T('conn_saved')));
        setTimeout(() => { window.BBManage && window.BBManage.refresh(); }, 600);
      } catch (e) { msg.textContent = '✗'; }
    };
    sec.append(save, msg);
    const note = document.createElement('p'); note.className = 'muted small'; note.textContent = T('conn_active') + ': ' + (conf.active || '—') + (conf.connected ? ' ✓' : '');
    sec.appendChild(note);
    root.appendChild(sec);
  }

  function render() {
    const root = $('#beheer'); if (!root) return;
    root.innerHTML = '';

    const wiz = document.createElement('button'); wiz.className = 'mng-add'; wiz.style.margin = '0 0 12px'; wiz.textContent = T('wiz_reopen');
    wiz.onclick = () => { if (window.openWizard) window.openWizard(); };
    root.appendChild(wiz);

    renderConnSection(root);

    // LOC'S
    root.appendChild(section('mng_locos', (sec) => {
      data.locos.forEach((l, idx) => {
        const row = document.createElement('div'); row.className = 'mng-row';
        const addr = input(l.addr, T('mng_addr')); addr.type = 'number'; addr.style.maxWidth = '80px';
        addr.oninput = () => l.addr = parseInt(addr.value, 10) || 0;
        const name = input(l.name, T('mng_name'), 'grow'); name.oninput = () => l.name = name.value;
        const color = input(l.color || '#4c8bf5'); color.type = 'color'; color.className = 'w-color'; color.oninput = () => l.color = color.value;
        const funcs = input((l.functions || []).map((f) => f.fn + ':' + f.label).join(','), T('mng_funcs'), 'grow');
        funcs.oninput = () => l.functions = funcs.value.split(',').map((p) => p.trim()).filter(Boolean).map((p) => { const [fn, ...lbl] = p.split(':'); return { fn: parseInt(fn, 10) || 0, label: (lbl.join(':') || ('F' + fn)).trim(), momentary: false }; });
        const photo = input(l.photo, T('mng_photo'), 'grow'); photo.oninput = () => l.photo = photo.value.trim() || undefined;
        const accel = input(l.accel == null ? 9 : l.accel, T('mng_accel')); accel.type = 'number'; accel.min = 1; accel.max = 28; accel.style.maxWidth = '70px'; accel.title = T('mng_accel_hint');
        accel.oninput = () => l.accel = Math.max(1, parseInt(accel.value, 10) || 9);
        const decel = input(l.decel == null ? 9 : l.decel, T('mng_decel')); decel.type = 'number'; decel.min = 1; decel.max = 28; decel.style.maxWidth = '70px'; decel.title = T('mng_decel_hint');
        decel.oninput = () => l.decel = Math.max(1, parseInt(decel.value, 10) || 9);
        const P = presets();
        const preset = document.createElement('select');
        preset.style.cssText = 'max-width:150px;background:#1f242e;color:#e8edf4;border:1px solid #2a313d;border-radius:8px;padding:7px';
        preset.innerHTML = '<option value="">' + T('preset_choose') + '</option>' + Object.keys(P).map((k) => '<option value="' + k + '">' + P[k].label + '</option>').join('');
        preset.onchange = () => { const p = P[preset.value]; if (p) { l.functions = p.funcs.map((x) => ({ ...x })); render(); } };
        row.append(addr, name, color, preset, funcs, photo, accel, decel, delBtn(() => { data.locos.splice(idx, 1); render(); }));
        sec.appendChild(row);
      });
    }, () => data.locos.push({ addr: 3, name: '', color: '#4c8bf5', functions: [{ fn: 0, label: 'Licht' }] })));

    // WISSELS
    root.appendChild(section('mng_turnouts', (sec) => {
      data.turnouts.forEach((t, idx) => {
        const row = document.createElement('div'); row.className = 'mng-row';
        const id = input(t.id, T('mng_id')); id.type = 'number'; id.style.maxWidth = '70px'; id.oninput = () => t.id = parseInt(id.value, 10) || 0;
        const name = input(t.name, T('mng_name'), 'grow'); name.oninput = () => t.name = name.value;
        row.append(id, name, delBtn(() => { data.turnouts.splice(idx, 1); render(); }));
        sec.appendChild(row);
      });
    }, () => data.turnouts.push({ id: (data.turnouts.length + 1), name: '' })));

    // BLOKKEN
    root.appendChild(section('mng_blocks', (sec) => {
      data.blocks.forEach((b, idx) => {
        const row = document.createElement('div'); row.className = 'mng-row';
        const id = input(b.id, T('mng_id')); id.type = 'number'; id.style.maxWidth = '70px'; id.oninput = () => b.id = parseInt(id.value, 10) || 0;
        const name = input(b.name, T('mng_name'), 'grow'); name.oninput = () => b.name = name.value;
        const sensor = input(b.sensor, T('mng_sensor')); sensor.type = 'number'; sensor.style.maxWidth = '90px'; sensor.oninput = () => b.sensor = parseInt(sensor.value, 10) || undefined;
        row.append(id, name, sensor, delBtn(() => { data.blocks.splice(idx, 1); render(); }));
        sec.appendChild(row);
      });
    }, () => data.blocks.push({ id: (data.blocks.length + 1), name: '' })));

    // RIJWEGEN
    root.appendChild(section('mng_routes', (sec) => {
      (data.routes = data.routes || []).forEach((r, idx) => {
        const row = document.createElement('div'); row.className = 'mng-row';
        const id = input(r.id, T('mng_id')); id.style.maxWidth = '70px'; id.oninput = () => r.id = id.value;
        const name = input(r.name, T('mng_name'), 'grow'); name.oninput = () => r.name = name.value;
        // wissels als "1:r,2:a"  (r=rechtdoor/false, a=afbuigend/true)
        const tn = input((r.turnouts || []).map((x) => x.id + ':' + (x.thrown ? 'a' : 'r')).join(','), T('mng_route_turnouts'), 'grow');
        tn.oninput = () => r.turnouts = tn.value.split(',').map((p) => p.trim()).filter(Boolean).map((p) => { const [id2, st] = p.split(':'); return { id: parseInt(id2, 10) || 0, thrown: /^(a|1|t)/i.test(st || '') }; });
        const bl = input((r.blocks || []).join(','), T('mng_route_blocks'));
        bl.oninput = () => r.blocks = bl.value.split(',').map((x) => parseInt(x.trim(), 10)).filter((n) => !isNaN(n));
        row.append(id, name, tn, bl, delBtn(() => { data.routes.splice(idx, 1); render(); }));
        sec.appendChild(row);
      });
    }, () => (data.routes = data.routes || []).push({ id: String((data.routes.length + 1)), name: '', turnouts: [], blocks: [] })));

    // RITTEN (automatisch)
    root.appendChild(section('mng_runs', (sec) => {
      (data.runs = data.runs || []).forEach((r, idx) => {
        const row = document.createElement('div'); row.className = 'mng-row';
        const id = input(r.id, T('mng_id')); id.style.maxWidth = '60px'; id.oninput = () => r.id = id.value;
        const name = input(r.name, T('mng_name'), 'grow'); name.oninput = () => r.name = name.value;
        const addr = input(r.addr, T('mng_run_loco')); addr.type = 'number'; addr.style.maxWidth = '80px'; addr.oninput = () => r.addr = parseInt(addr.value, 10) || 0;
        const loopL = document.createElement('label'); loopL.style.cssText = 'display:flex;align-items:center;gap:5px;font-size:13px;color:var(--muted)';
        const loop = document.createElement('input'); loop.type = 'checkbox'; loop.checked = !!r.loop; loop.style.minWidth = '18px'; loop.onchange = () => r.loop = loop.checked;
        loopL.append(loop, document.createTextNode(T('mng_run_loop')));
        // stappen als "route:speed:wait,route:speed:wait"
        const steps = input((r.steps || []).map((x) => x.route + ':' + (x.speed || 50) + ':' + (x.wait || 2)).join(','), T('mng_run_steps'), 'grow');
        steps.oninput = () => r.steps = steps.value.split(',').map((p) => p.trim()).filter(Boolean).map((p) => { const [route, sp, w] = p.split(':'); return { route: (route || '').trim(), speed: parseInt(sp, 10) || 50, wait: parseInt(w, 10) || 2 }; });
        row.append(id, name, addr, loopL, steps, delBtn(() => { data.runs.splice(idx, 1); render(); }));
        sec.appendChild(row);
      });
    }, () => (data.runs = data.runs || []).push({ id: String((data.runs.length + 1)), name: '', addr: 3, loop: true, steps: [] })));

    // PENDELTREINEN — loc heen-en-weer tussen twee blokken
    root.appendChild(section('mng_shuttles', (sec) => {
      (data.shuttles = data.shuttles || []).forEach((sh, idx) => {
        const row = document.createElement('div'); row.className = 'mng-row';
        const id = input(sh.id, T('mng_id')); id.style.maxWidth = '60px'; id.oninput = () => sh.id = id.value;
        const name = input(sh.name, T('mng_name'), 'grow'); name.oninput = () => sh.name = name.value;
        const addr = input(sh.addr, T('mng_run_loco')); addr.type = 'number'; addr.style.maxWidth = '80px'; addr.oninput = () => sh.addr = parseInt(addr.value, 10) || 0;
        const from = input(sh.from, T('mng_sh_from')); from.style.maxWidth = '90px'; from.oninput = () => sh.from = from.value.trim();
        const to = input(sh.to, T('mng_sh_to')); to.style.maxWidth = '90px'; to.oninput = () => sh.to = to.value.trim();
        const speed = input(sh.speed, T('mng_sh_speed')); speed.type = 'number'; speed.style.maxWidth = '80px'; speed.oninput = () => sh.speed = parseInt(speed.value, 10) || 40;
        const wait = input(sh.wait, T('mng_sh_wait')); wait.type = 'number'; wait.style.maxWidth = '80px'; wait.oninput = () => sh.wait = parseInt(wait.value, 10) || 3;
        row.append(id, name, addr, from, to, speed, wait, delBtn(() => { data.shuttles.splice(idx, 1); render(); }));
        sec.appendChild(row);
      });
    }, () => (data.shuttles = data.shuttles || []).push({ id: String((data.shuttles.length + 1)), name: '', addr: 3, from: '', to: '', speed: 40, wait: 3 })));

    // DIENSTREGELING (tijdtabel) — ritten starten op modelklok-tijd
    root.appendChild(section('mng_tt', (sec) => {
      const fr = document.createElement('div'); fr.className = 'mng-row';
      const fl = document.createElement('span'); fl.className = 'muted small'; fl.style.alignSelf = 'center'; fl.textContent = T('mng_clockfactor');
      const ff = input(data.clockFactor || 8); ff.type = 'number'; ff.style.maxWidth = '80px'; ff.oninput = () => data.clockFactor = parseInt(ff.value, 10) || 8;
      fr.append(fl, ff); sec.appendChild(fr);
      (data.timetable = data.timetable || []).forEach((e, idx) => {
        const row = document.createElement('div'); row.className = 'mng-row';
        const time = input(e.time, T('mng_tt_time')); time.style.maxWidth = '110px'; time.oninput = () => e.time = time.value.trim();
        const run = input(e.run, T('mng_tt_run')); run.style.maxWidth = '110px'; run.oninput = () => e.run = run.value.trim();
        row.append(time, run, delBtn(() => { data.timetable.splice(idx, 1); render(); }));
        sec.appendChild(row);
      });
    }, () => (data.timetable = data.timetable || []).push({ time: '08:00', run: '' })));

    // TRACTIE (consists) — leden als "3,7r" (r = omgekeerd ingezet)
    root.appendChild(section('mng_consists', (sec) => {
      (data.consists = data.consists || []).forEach((cs, idx) => {
        const row = document.createElement('div'); row.className = 'mng-row';
        const id = input(cs.id, T('mng_id')); id.style.maxWidth = '60px'; id.oninput = () => cs.id = id.value;
        const name = input(cs.name, T('mng_name'), 'grow'); name.oninput = () => cs.name = name.value;
        const mem = input((cs.members || []).map((m) => m.addr + (m.reverse ? 'r' : '')).join(','), T('mng_consist_members'), 'grow');
        mem.oninput = () => cs.members = mem.value.split(',').map((p) => p.trim()).filter(Boolean).map((p) => ({ addr: parseInt(p, 10) || 0, reverse: /r$/i.test(p) }));
        row.append(id, name, mem, delBtn(() => { data.consists.splice(idx, 1); render(); }));
        sec.appendChild(row);
      });
    }, () => (data.consists = data.consists || []).push({ id: String((data.consists.length + 1)), name: '', color: '#f0b53a', members: [] })));

    // SEINEN — seinbeeld volgt blokbezetting
    root.appendChild(section('mng_signals', (sec) => {
      (data.signals = data.signals || []).forEach((s, idx) => {
        const row = document.createElement('div'); row.className = 'mng-row';
        const id = input(s.id, T('mng_id')); id.style.maxWidth = '60px'; id.oninput = () => s.id = id.value;
        const name = input(s.name, T('mng_name'), 'grow'); name.oninput = () => s.name = name.value;
        const watch = input(s.watch, T('mng_sig_watch')); watch.style.maxWidth = '110px'; watch.title = T('mng_sig_watch_hint'); watch.oninput = () => s.watch = watch.value.trim();
        const next = input(s.next, T('mng_sig_next')); next.style.maxWidth = '110px'; next.title = T('mng_sig_next_hint'); next.oninput = () => s.next = next.value.trim();
        const sys = document.createElement('select'); sys.style.cssText = 'max-width:130px;background:#1f242e;color:#e8edf4;border:1px solid #2a313d;border-radius:8px;padding:7px';
        [['simple', T('sig_sys_simple')], ['nl', 'NS (NL)'], ['db', 'DB (DE)']].forEach(([v, t]) => sys.add(new Option(t, v, false, (s.sys || 'simple') === v)));
        sys.onchange = () => s.sys = sys.value;
        const addr = input(s.addr, T('mng_sig_addr')); addr.type = 'number'; addr.style.maxWidth = '90px'; addr.title = T('mng_sig_addr_hint'); addr.oninput = () => s.addr = parseInt(addr.value, 10) || 0;
        const addr2 = input(s.addr2, T('mng_sig_addr2')); addr2.type = 'number'; addr2.style.maxWidth = '90px'; addr2.title = T('mng_sig_addr2_hint'); addr2.oninput = () => s.addr2 = parseInt(addr2.value, 10) || 0;
        row.append(id, name, sys, watch, next, addr, addr2, delBtn(() => { data.signals.splice(idx, 1); render(); }));
        sec.appendChild(row);
      });
    }, () => (data.signals = data.signals || []).push({ id: String((data.signals.length + 1)), name: '', watch: '', addr: 0 })));

    // DRAAISCHIJVEN
    root.appendChild(section('mng_tts', (sec) => {
      (data.turntables = data.turntables || []).forEach((tt, idx) => {
        const row = document.createElement('div'); row.className = 'mng-row';
        const id = input(tt.id, T('mng_id')); id.style.maxWidth = '60px'; id.oninput = () => tt.id = id.value;
        const name = input(tt.name, T('mng_name'), 'grow'); name.oninput = () => tt.name = name.value;
        const addr = input(tt.addr, T('mng_tt_addr')); addr.type = 'number'; addr.style.maxWidth = '110px'; addr.oninput = () => tt.addr = parseInt(addr.value, 10) || 0;
        const pos = input(tt.positions, T('mng_tt_pos')); pos.type = 'number'; pos.style.maxWidth = '90px'; pos.oninput = () => tt.positions = parseInt(pos.value, 10) || 1;
        row.append(id, name, addr, pos, delBtn(() => { data.turntables.splice(idx, 1); render(); }));
        sec.appendChild(row);
      });
    }, () => (data.turntables = data.turntables || []).push({ id: String((data.turntables.length + 1)), name: '', addr: 0, positions: 4, pos: 0 })));

    // WEBCAM
    { const sec = document.createElement('div'); sec.className = 'mng-sec';
      const h = document.createElement('h2'); h.textContent = T('mng_webcam'); sec.appendChild(h);
      const row = document.createElement('div'); row.className = 'mng-row';
      const url = input(data.webcamUrl, 'https://…/snapshot.jpg', 'grow'); url.oninput = () => data.webcamUrl = url.value.trim();
      row.appendChild(url); sec.appendChild(row); root.appendChild(sec);
    }

    // DECODER / CV (PoM) — directe actie, geen opslag
    { const sec = document.createElement('div'); sec.className = 'mng-sec';
      const h = document.createElement('h2'); h.textContent = T('mng_cv'); sec.appendChild(h);
      const row = document.createElement('div'); row.className = 'mng-row';
      const a = input('', T('cv_addr')); a.type = 'number'; a.style.maxWidth = '100px';
      const cv = input('', T('cv_cv')); cv.type = 'number'; cv.style.maxWidth = '90px';
      const v = input('', T('cv_val')); v.type = 'number'; v.style.maxWidth = '90px';
      const btn = document.createElement('button'); btn.className = 'mng-add'; btn.textContent = T('cv_write');
      const msg = document.createElement('span'); msg.className = 'muted small';
      btn.onclick = async () => { await cmd({ type: 'cv_write', addr: parseInt(a.value, 10) || 0, cv: parseInt(cv.value, 10) || 0, value: parseInt(v.value, 10) || 0 }); msg.textContent = T('cv_done'); setTimeout(() => msg.textContent = '', 1800); };
      row.append(a, cv, v, btn, msg); sec.appendChild(row); root.appendChild(sec);
    }

    // ONDERHOUD / DRAAIUREN
    { const sec = document.createElement('div'); sec.className = 'mng-sec';
      const h = document.createElement('h2'); h.textContent = T('mng_maint'); sec.appendChild(h);
      const rt = data.runtime || {};
      const addrs = (data.locos || []).map((l) => l.addr);
      if (!addrs.length) { const p = document.createElement('p'); p.className = 'muted small'; p.textContent = T('maint_none'); sec.appendChild(p); }
      (data.locos || []).forEach((l) => {
        const row = document.createElement('div'); row.className = 'mng-row';
        const hrs = ((rt[l.addr] || 0) / 3600);
        const lbl = document.createElement('span'); lbl.className = 'grow'; lbl.style.alignSelf = 'center';
        lbl.textContent = (l.name || ('Loc ' + l.addr)) + ' — ' + hrs.toFixed(1) + ' ' + T('maint_hours');
        const btn = document.createElement('button'); btn.className = 'mng-del'; btn.style.width = 'auto'; btn.style.padding = '0 10px'; btn.textContent = T('maint_service');
        btn.onclick = async () => { await cmd({ type: 'loco_service', addr: l.addr, note: '' }); };
        row.append(lbl, btn); sec.appendChild(row);
      });
      root.appendChild(sec);
    }

    // SCHADUWSTATION / OPSTELSPOREN
    root.appendChild(section('mng_staging', (sec) => {
      (data.staging = data.staging || []).forEach((y, idx) => {
        const row = document.createElement('div'); row.className = 'mng-row';
        const id = input(y.id, T('mng_id')); id.style.maxWidth = '60px'; id.oninput = () => y.id = id.value;
        const name = input(y.name, T('mng_name'), 'grow'); name.oninput = () => y.name = name.value;
        const tracks = input((y.tracks || []).join(','), T('mng_st_tracks'), 'grow');
        tracks.oninput = () => y.tracks = tracks.value.split(',').map((x) => x.trim()).filter(Boolean);
        row.append(id, name, tracks, delBtn(() => { data.staging.splice(idx, 1); render(); }));
        sec.appendChild(row);
      });
    }, () => (data.staging = data.staging || []).push({ id: String((data.staging.length + 1)), name: '', tracks: [] })));

    // IMPORT (Rocrail plan.xml / JMRI roster.xml)
    { const sec = document.createElement('div'); sec.className = 'mng-sec';
      const h = document.createElement('h2'); h.textContent = T('mng_import'); sec.appendChild(h);
      const p = document.createElement('p'); p.className = 'muted small'; p.textContent = T('mng_import_p'); sec.appendChild(p);
      const row = document.createElement('div'); row.className = 'mng-row';
      const file = document.createElement('input'); file.type = 'file'; file.accept = '.xml,text/xml'; file.className = 'grow';
      const btn = document.createElement('button'); btn.className = 'mng-add'; btn.textContent = T('mng_import_btn');
      const msg = document.createElement('span'); msg.className = 'muted small'; msg.style.alignSelf = 'center';
      btn.onclick = () => {
        const f = file.files && file.files[0]; if (!f) { msg.textContent = '—'; return; }
        const fr = new FileReader();
        fr.onload = () => post('api/import', { xml: String(fr.result) }).then((r) => r.json()).then((d) => {
          msg.textContent = d.ok ? ('✓ ' + d.locos + ' loc · ' + d.turnouts + ' wissels · ' + d.blocks + ' blokken') : '✗';
          if (d.ok) setTimeout(() => window.BBManage.refresh(), 400);
        }).catch(() => { msg.textContent = '✗'; });
        fr.readAsText(f);
      };
      row.append(file, btn, msg); sec.appendChild(row); root.appendChild(sec);
    }

    // CLUB / MULTI-OPERATOR
    { const sec = document.createElement('div'); sec.className = 'mng-sec';
      const h = document.createElement('h2'); h.textContent = T('mng_club'); sec.appendChild(h);
      const hint = document.createElement('p'); hint.className = 'muted small'; hint.textContent = T('club_hint'); sec.appendChild(hint);
      const tr = document.createElement('div'); tr.className = 'mng-row';
      const lbl = document.createElement('label'); lbl.style.cssText = 'display:flex;align-items:center;gap:8px;font-size:14px';
      const cb = document.createElement('input'); cb.type = 'checkbox'; cb.id = 'club-tog'; cb.style.minWidth = '18px';
      const cst = (window.BB && window.BB.state && window.BB.state().club) || { enabled: false, operators: [] };
      cb.checked = !!cst.enabled; cb.onchange = () => post('api/club', { enabled: cb.checked });
      lbl.append(cb, document.createTextNode(T('club_enable'))); tr.appendChild(lbl); sec.appendChild(tr);
      const opsd = document.createElement('div'); opsd.id = 'club-ops'; sec.appendChild(opsd);
      root.appendChild(sec);
      fillClubOps(cst);
    }

    // INTEGRATIES (open API + webhooks, anomalie/push, Home Assistant MQTT, lichtscènes)
    { const sec = document.createElement('div'); sec.className = 'mng-sec';
      const h = document.createElement('h2'); h.textContent = T('mng_integrations'); sec.appendChild(h);
      const I = integ || {};
      const gi = Object.assign({ apiKey: '', webhooks: [], anomaly: { stuckSec: 600, enabled: true }, cloudPush: { url: '', key: '' } }, I.integrations || {});
      const gl = Object.assign({ devices: [], dayStart: 420, nightStart: 1260 }, I.lights || {});
      const gm = Object.assign({ enabled: false, host: '', port: 1883, username: '', password: '' }, I.mqtt || {});
      const lbl = (t) => { const d = document.createElement('div'); d.className = 'muted small'; d.style.margin = '10px 0 2px'; d.textContent = t; return d; };

      // Open API + webhooks
      sec.appendChild(lbl(T('integ_api')));
      const apiRow = document.createElement('div'); apiRow.className = 'mng-row';
      const apiKey = input(gi.apiKey, T('integ_apikey'), 'grow'); apiKey.oninput = () => gi.apiKey = apiKey.value.trim();
      const genK = mkBtn(T('integ_gen'), () => { gi.apiKey = (Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)).slice(0, 28); apiKey.value = gi.apiKey; });
      apiRow.append(apiKey, genK); sec.appendChild(apiRow);
      const hookHint = document.createElement('div'); hookHint.className = 'muted small'; hookHint.textContent = T('integ_webhooks_hint'); sec.appendChild(hookHint);
      const hooks = document.createElement('textarea'); hooks.rows = 2; hooks.style.cssText = 'width:100%;background:#1f242e;color:#e8edf4;border:1px solid #2a313d;border-radius:8px;padding:8px;font-family:monospace;font-size:12px';
      hooks.value = (gi.webhooks || []).join('\n'); hooks.oninput = () => gi.webhooks = hooks.value.split('\n').map((s) => s.trim()).filter(Boolean); sec.appendChild(hooks);

      // Anomaliedetectie + push
      sec.appendChild(lbl(T('integ_anomaly')));
      const anRow = document.createElement('div'); anRow.className = 'mng-row';
      const anOn = document.createElement('label'); anOn.style.cssText = 'display:flex;align-items:center;gap:6px;font-size:13px';
      const anCb = document.createElement('input'); anCb.type = 'checkbox'; anCb.checked = gi.anomaly.enabled !== false; anCb.onchange = () => gi.anomaly.enabled = anCb.checked;
      anOn.append(anCb, document.createTextNode(T('integ_anomaly_on')));
      const stuck = input(gi.anomaly.stuckSec, T('integ_stuck')); stuck.type = 'number'; stuck.style.maxWidth = '100px'; stuck.oninput = () => gi.anomaly.stuckSec = parseInt(stuck.value, 10) || 600;
      const pushUrl = input(gi.cloudPush.url, T('integ_pushurl'), 'grow'); pushUrl.oninput = () => gi.cloudPush.url = pushUrl.value.trim();
      const pushKey = input(gi.cloudPush.key, T('integ_pushkey')); pushKey.style.maxWidth = '160px'; pushKey.oninput = () => gi.cloudPush.key = pushKey.value.trim();
      anRow.append(anOn, stuck, pushUrl, pushKey); sec.appendChild(anRow);

      // Home Assistant (MQTT)
      sec.appendChild(lbl(T('integ_ha')));
      const haRow = document.createElement('div'); haRow.className = 'mng-row'; haRow.style.flexWrap = 'wrap';
      const haOn = document.createElement('label'); haOn.style.cssText = 'display:flex;align-items:center;gap:6px;font-size:13px';
      const haCb = document.createElement('input'); haCb.type = 'checkbox'; haCb.checked = !!gm.enabled; haCb.onchange = () => gm.enabled = haCb.checked;
      haOn.append(haCb, document.createTextNode(T('integ_ha_on')));
      const mh = input(gm.host, 'MQTT-broker IP'); mh.oninput = () => gm.host = mh.value.trim();
      const mp = input(gm.port, 'poort'); mp.type = 'number'; mp.style.maxWidth = '80px'; mp.oninput = () => gm.port = parseInt(mp.value, 10) || 1883;
      const mu = input(gm.username, T('integ_user')); mu.style.maxWidth = '130px'; mu.oninput = () => gm.username = mu.value.trim();
      const mpw = input(gm.password, T('integ_pass')); mpw.type = 'password'; mpw.style.maxWidth = '130px'; mpw.oninput = () => gm.password = mpw.value;
      const haStat = document.createElement('span'); haStat.className = 'muted small'; haStat.style.alignSelf = 'center'; haStat.textContent = (I.ha && I.ha.connected) ? '🟢' : '';
      haRow.append(haOn, mh, mp, mu, mpw, haStat); sec.appendChild(haRow);

      // Lichtscènes (dag/nacht)
      sec.appendChild(lbl(T('integ_lights')));
      const ltHint = document.createElement('div'); ltHint.className = 'muted small'; ltHint.textContent = T('integ_lights_hint'); sec.appendChild(ltHint);
      const ltTimes = document.createElement('div'); ltTimes.className = 'mng-row';
      const hhmm = (m) => String(Math.floor(m / 60)).padStart(2, '0') + ':' + String(m % 60).padStart(2, '0');
      const m2 = (s) => { const [h, mm] = (s || '0:0').split(':').map((x) => parseInt(x, 10) || 0); return h * 60 + mm; };
      const dStart = input(hhmm(gl.dayStart), T('integ_day')); dStart.type = 'time'; dStart.style.maxWidth = '120px'; dStart.oninput = () => gl.dayStart = m2(dStart.value);
      const nStart = input(hhmm(gl.nightStart), T('integ_night')); nStart.type = 'time'; nStart.style.maxWidth = '120px'; nStart.oninput = () => gl.nightStart = m2(nStart.value);
      ltTimes.append(dStart, nStart); sec.appendChild(ltTimes);
      const ltDev = document.createElement('textarea'); ltDev.rows = 3; ltDev.style.cssText = hooks.style.cssText;
      ltDev.value = JSON.stringify(gl.devices || [], null, 0);
      ltDev.oninput = () => { try { gl.devices = JSON.parse(ltDev.value || '[]'); ltDev.style.borderColor = '#2a313d'; } catch (e) { ltDev.style.borderColor = '#e25555'; } };
      sec.appendChild(ltDev);
      const ltBtns = document.createElement('div'); ltBtns.className = 'mng-row';
      ltBtns.append(mkBtn('☀️ ' + T('integ_test_day'), () => cmd({ type: 'light_scene', scene: 'day' })), mkBtn('🌙 ' + T('integ_test_night'), () => cmd({ type: 'light_scene', scene: 'night' }))); sec.appendChild(ltBtns);

      const sv = document.createElement('div'); sv.className = 'mng-row';
      const ib = document.createElement('button'); ib.className = 'mng-save'; ib.style.margin = '0'; ib.textContent = T('integ_save');
      const im = document.createElement('div'); im.className = 'mng-saved';
      ib.onclick = async () => {
        try { const r = await post('api/integrations', { integrations: gi, lights: gl, mqtt: gm }); const j = await r.json(); im.textContent = j.ok ? T('saved') : '✗'; if (j.ok) integ = { integrations: gi, lights: gl, mqtt: gm }; setTimeout(() => im.textContent = '', 1800); }
        catch (e) { im.textContent = '✗'; }
      };
      sv.append(ib, im); sec.appendChild(sv);
      root.appendChild(sec);
    }

    // COMMUNITY-BIBLIOTHEEK (gedeelde baanplannen / loc-profielen)
    { const sec = document.createElement('div'); sec.className = 'mng-sec';
      const h = document.createElement('h2'); h.textContent = T('mng_library'); sec.appendChild(h);
      const hint = document.createElement('p'); hint.className = 'muted small'; hint.textContent = T('lib_hint'); sec.appendChild(hint);
      const LIB = 'https://modelrailpro.com/library';
      const bar = document.createElement('div'); bar.className = 'mng-row'; bar.style.flexWrap = 'wrap';
      const q = input('', T('lib_search'), 'grow');
      const typeSel = document.createElement('select'); typeSel.style.cssText = 'background:#1f242e;color:#e8edf4;border:1px solid #2a313d;border-radius:8px;padding:7px';
      [['', T('lib_all')], ['roster', T('lib_roster')], ['loco', T('lib_loco')], ['route', T('lib_route')]].forEach(([v, t]) => typeSel.add(new Option(t, v)));
      const listDiv = document.createElement('div'); listDiv.style.marginTop = '8px';
      const loadList = async () => {
        listDiv.innerHTML = '<span class="muted small">⏳</span>';
        try {
          const d = await (await fetch(LIB + '/list.php?type=' + encodeURIComponent(typeSel.value) + '&q=' + encodeURIComponent(q.value))).json();
          listDiv.innerHTML = '';
          if (!d.items || !d.items.length) { listDiv.innerHTML = '<span class="muted small">' + T('lib_empty') + '</span>'; return; }
          d.items.forEach((it) => {
            const row = document.createElement('div'); row.className = 'mng-row'; row.style.alignItems = 'center';
            const info = document.createElement('span'); info.className = 'grow'; info.innerHTML = '<b>' + (it.name || '') + '</b> <span class="muted small">· ' + it.type + (it.author ? ' · ' + it.author : '') + ' · ⬇ ' + it.downloads + '</span>' + (it.descr ? '<br><span class="muted small">' + it.descr + '</span>' : '');
            const get = mkBtn('⬇ ' + T('lib_get'), async () => {
              try {
                const r = await (await fetch(LIB + '/get.php?id=' + it.id)).json();
                if (!r.ok) return;
                importItem(r.item);
                get.textContent = '✓'; setTimeout(() => get.textContent = '⬇ ' + T('lib_get'), 1500);
              } catch (e) {}
            });
            row.append(info, get); listDiv.appendChild(row);
          });
        } catch (e) { listDiv.innerHTML = '<span class="muted small">✗ ' + T('lib_offline') + '</span>'; }
      };
      function importItem(item) {
        const dd = item.data || {};
        if (item.type === 'loco') { data.locos = data.locos || []; data.locos.push(dd); }
        else if (item.type === 'route') { data.routes = data.routes || []; data.routes.push(dd); }
        else if (item.type === 'roster') { ['locos', 'turnouts', 'blocks', 'routes', 'signals', 'turntables', 'runs', 'shuttles'].forEach((k) => { if (Array.isArray(dd[k])) data[k] = dd[k]; }); }
        render();
        alert(T('lib_imported'));
      }
      bar.append(q, typeSel, mkBtn('🔍 ' + T('lib_load'), loadList)); sec.appendChild(bar); sec.appendChild(listDiv);

      // delen
      const pub = document.createElement('div'); pub.className = 'mng-row'; pub.style.cssText = 'flex-wrap:wrap;margin-top:12px;border-top:1px solid var(--line);padding-top:12px';
      const pName = input('', T('lib_pub_name')); const pAuthor = input('', T('lib_pub_author')); pAuthor.style.maxWidth = '150px';
      const pType = document.createElement('select'); pType.style.cssText = typeSel.style.cssText;
      [['roster', T('lib_pub_roster')], ['loco', T('lib_pub_loco')]].forEach(([v, t]) => pType.add(new Option(t, v)));
      const pMsg = document.createElement('span'); pMsg.className = 'muted small'; pMsg.style.alignSelf = 'center';
      const pubBtn = mkBtn('📤 ' + T('lib_publish'), async () => {
        if (!pName.value.trim()) { pMsg.textContent = T('lib_pub_needname'); return; }
        let payload;
        if (pType.value === 'loco') { const l = (data.locos || [])[0]; if (!l) { pMsg.textContent = T('lib_pub_noloco'); return; } payload = l; }
        else payload = { locos: data.locos, turnouts: data.turnouts, blocks: data.blocks, routes: data.routes, signals: data.signals, turntables: data.turntables, runs: data.runs, shuttles: data.shuttles };
        pMsg.textContent = '⏳';
        try { const r = await (await fetch(LIB + '/publish.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: pType.value, name: pName.value.trim(), author: pAuthor.value.trim(), data: payload }) })).json(); pMsg.textContent = r.ok ? T('lib_published') : ('✗ ' + (r.error || '')); }
        catch (e) { pMsg.textContent = '✗'; }
      });
      pub.append(pType, pName, pAuthor, pubBtn, pMsg); sec.appendChild(pub);
      root.appendChild(sec);
    }

    // EXPORT (CSV / JSON / print)
    { const sec = document.createElement('div'); sec.className = 'mng-sec';
      const h = document.createElement('h2'); h.textContent = T('mng_export'); sec.appendChild(h);
      const hint = document.createElement('p'); hint.className = 'muted small'; hint.textContent = T('export_hint'); sec.appendChild(hint);
      const row = document.createElement('div'); row.className = 'mng-row'; row.style.flexWrap = 'wrap';

      const csvCell = (v) => { const s = (v == null ? '' : String(v)); return /[",\n;]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s; };
      const toCsv = (rows) => rows.map((r) => r.map(csvCell).join(';')).join('\r\n');
      const dl = (name, text, mime) => {
        const blob = new Blob(['﻿' + text], { type: (mime || 'text/csv') + ';charset=utf-8' });
        const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = name;
        document.body.appendChild(a); a.click(); setTimeout(() => { URL.revokeObjectURL(a.href); a.remove(); }, 1000);
      };
      const stamp = new Date().toISOString().slice(0, 10);

      const bLocos = mkBtn('📋 ' + T('export_locos'), () => {
        const rows = [[T('mng_addr'), T('mng_name'), T('mng_funcs'), T('mng_accel'), T('mng_decel'), T('maint_hours')]];
        (data.locos || []).forEach((l) => rows.push([l.addr, l.name || '', (l.functions || []).map((f) => f.fn + ':' + f.label).join(' '), l.accel == null ? 9 : l.accel, l.decel == null ? 9 : l.decel, (((data.runtime || {})[l.addr] || 0) / 3600).toFixed(1)]));
        dl('modelrailpro-locs-' + stamp + '.csv', toCsv(rows));
      });
      const bInv = mkBtn('📦 ' + T('export_inventory'), () => {
        const rows = [[T('export_type'), T('mng_id'), T('mng_name'), T('mng_addr'), 'detail']];
        (data.locos || []).forEach((l) => rows.push(['loc', l.addr, l.name || '', l.addr, (l.functions || []).length + ' functies']));
        (data.turnouts || []).forEach((t) => rows.push(['wissel', t.id, t.name || '', t.addr || '', '']));
        (data.blocks || []).forEach((b) => rows.push(['blok', b.id, b.name || '', '', (b.cm ? b.cm + ' cm' : '')]));
        (data.signals || []).forEach((s) => rows.push(['sein', s.id, s.name || '', s.addr || '', s.watch || '']));
        (data.turntables || []).forEach((t) => rows.push(['draaischijf', t.id, t.name || '', t.addr || '', (t.positions || '') + ' posities']));
        dl('modelrailpro-inventaris-' + stamp + '.csv', toCsv(rows));
      });
      const bJson = mkBtn('💾 ' + T('export_config'), () => {
        dl('modelrailpro-baan-' + stamp + '.json', JSON.stringify({ version: 1, exported: stamp, roster: data }, null, 2), 'application/json');
      });
      const bPrint = mkBtn('🖨️ ' + T('export_print'), () => printSchema());

      row.append(bLocos, bInv, bJson, bPrint); sec.appendChild(row); root.appendChild(sec);
    }

    // OPSLAAN
    const save = document.createElement('button'); save.className = 'mng-save'; save.textContent = T('mng_save');
    const msg = document.createElement('div'); msg.className = 'mng-saved';
    save.onclick = async () => {
      try {
        const r = await fetch('api/roster', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) });
        const j = await r.json();
        msg.textContent = j.ok ? T('saved') : '✗';
        setTimeout(() => { msg.textContent = ''; }, 1800);
      } catch (e) { msg.textContent = '✗'; }
    };
    root.append(save, msg);
  }

  // koppel aan het live roster uit app.js
  window.BBManage = {
    refresh() {
      if (window.BB) {
        const r = window.BB.roster();
        data = JSON.parse(JSON.stringify({ locos: r.locos || [], turnouts: r.turnouts || [], blocks: r.blocks || [], routes: r.routes || [], runs: r.runs || [], consists: r.consists || [], timetable: r.timetable || [], clockFactor: r.clockFactor || 8, signals: r.signals || [], turntables: r.turntables || [], webcamUrl: r.webcamUrl || '', runtime: r.runtime || {}, maintenance: r.maintenance || [], shuttles: r.shuttles || [], staging: r.staging || [] }));
        render();
      }
      fetch('api/config').then((r) => r.json()).then((c) => { conf = c; render(); }).catch(() => {});
      fetch('api/integrations').then((r) => r.json()).then((c) => { integ = c; render(); }).catch(() => {});
    },
    club(c) { const tog = document.getElementById('club-tog'); if (tog && document.activeElement !== tog) tog.checked = !!c.enabled; fillClubOps(c); },
    diag() { updateDiag(); },
  };
  document.addEventListener('langchange', render);
  // eerste vulling zodra roster binnen is (app.js roept BBManage.refresh aan)
})();
