(function () {
  const NS = 'http://www.w3.org/2000/svg';

  function el(tag, attrs, text) {
    const node = document.createElementNS(NS, tag);
    Object.entries(attrs || {}).forEach(([k, v]) => node.setAttribute(k, String(v)));
    if (text != null) node.textContent = text;
    return node;
  }

  function clear(root) {
    while (root.firstChild) root.removeChild(root.firstChild);
  }

  function statusClass(status) {
    const s = (status || 'unknown').toLowerCase();
    if (['online', 'running', 'up'].includes(s)) return 'online';
    if (['offline', 'down', 'stopped'].includes(s)) return 'offline';
    return 'degraded';
  }

  function flattenTree(nodes, parentId) {
    const out = [];
    (nodes || []).forEach((n) => {
      out.push({ ...n, parentId: parentId || null });
      out.push(...flattenTree(n.children || [], n.id));
    });
    return out;
  }

  function layoutNodes(flat, width) {
    const byType = { hardware: [], proxmox: [], vm: [], service: [] };
    flat.forEach((n) => {
      const t = n.type || 'service';
      (byType[t] || byType.service).push(n);
    });

    const positions = {};
    positions['hardware-r340'] = { x: width / 2, y: 70 };
    positions['proxmox-host'] = { x: width / 2, y: 190 };

    const vm = byType.vm[0];
    if (vm) positions[vm.id] = { x: width / 2, y: 310 };

    const services = byType.service;
    const count = Math.max(services.length, 1);
    const pad = 70;
    const span = width - pad * 2;
    services.forEach((s, i) => {
      positions[s.id] = {
        x: pad + (span * (i + 0.5)) / count,
        y: 430,
      };
    });

    flat.forEach((n, i) => {
      if (!positions[n.id]) {
        positions[n.id] = { x: 100 + (i % 6) * (width / 7), y: 430 };
      }
    });

    positions['mon-sysops'] = { x: width * 0.32, y: 570 };
    positions['mon-research'] = { x: width * 0.68, y: 570 };
    return positions;
  }

  function drawLayerBands(svg, layers, width) {
    const g = el('g', { class: 'ops-layers' });
    (layers || []).forEach((layer) => {
      g.appendChild(el('line', {
        x1: 36, y1: layer.y - 28, x2: width - 36, y2: layer.y - 28,
        class: 'ops-layer-line',
      }));
      g.appendChild(el('text', {
        x: 14, y: layer.y - 33, class: 'ops-layer-label',
      }, layer.label));
    });
    svg.appendChild(g);
  }

  function drawEdges(svg, flat, positions) {
    const g = el('g', { class: 'ops-edges' });
    flat.forEach((n) => {
      if (!n.parentId) return;
      const from = positions[n.parentId];
      const to = positions[n.id];
      if (!from || !to) return;
      const yOffFrom = n.parentId === 'hardware-r340' ? 38 : 32;
      const yOffTo = n.type === 'hardware' ? -38 : n.type === 'service' ? -22 : -30;
      g.appendChild(el('line', {
        x1: from.x, y1: from.y + yOffFrom, x2: to.x, y2: to.y + yOffTo,
        class: 'topology-line',
      }));
      g.appendChild(el('line', {
        x1: from.x, y1: from.y + yOffFrom, x2: to.x, y2: to.y + yOffTo,
        class: 'pulse-line',
      }));
    });
    svg.appendChild(g);
  }

  function drawHardwareNode(svg, n, pos) {
    const cls = statusClass(n.status);
    const g = el('g', {
      class: `ops-node-group node-hardware status-${cls}`,
      transform: `translate(${pos.x},${pos.y})`,
    });
    g.appendChild(el('rect', { x: -95, y: -36, width: 190, height: 72, rx: 8, class: 'ops-hardware-box' }));
    g.appendChild(el('rect', { x: -88, y: -28, width: 12, height: 56, rx: 2, class: 'ops-hardware-slot' }));
    g.appendChild(el('rect', { x: -70, y: -28, width: 12, height: 56, rx: 2, class: 'ops-hardware-slot' }));
    g.appendChild(el('text', { x: -48, y: -6, class: 'ops-node-title ops-title-left' }, 'Dell PowerEdge'));
    g.appendChild(el('text', { x: -48, y: 14, class: 'ops-node-sub ops-title-left' }, 'R340 · Bare metal'));
    g.appendChild(el('text', { x: -48, y: 32, class: 'ops-node-role ops-title-left' }, cls.toUpperCase()));
    svg.appendChild(g);
  }

  function drawNode(svg, n, pos) {
    if (n.type === 'hardware') {
      drawHardwareNode(svg, n, pos);
      return;
    }
    const cls = statusClass(n.status);
    const g = el('g', {
      class: `ops-node-group node-${n.type || 'service'} status-${cls}`,
      transform: `translate(${pos.x},${pos.y})`,
    });
    const r = n.type === 'proxmox' ? 38 : n.type === 'vm' ? 32 : 24;
    g.appendChild(el('circle', { r: r, class: 'ops-node-circle' }));

    const title = (n.label || n.id).split('·')[0].trim();
    g.appendChild(el('text', { y: -6, class: 'ops-node-title' }, title.length > 18 ? title.slice(0, 16) + '…' : title));

    if (n.type === 'vm' && n.vmid) {
      g.appendChild(el('text', { y: 10, class: 'ops-node-sub' }, 'ID ' + n.vmid + ' · ' + cls.toUpperCase()));
    } else {
      g.appendChild(el('text', { y: 10, class: 'ops-node-sub' }, cls.toUpperCase()));
    }

    if (n.role) {
      const role = n.role.length > 28 ? n.role.slice(0, 26) + '…' : n.role;
      g.appendChild(el('text', { y: 24, class: 'ops-node-role' }, role));
    }

    if (n.url && n.type === 'service') {
      const link = el('a', { href: n.url, target: '_blank', rel: 'noopener' });
      link.appendChild(g);
      svg.appendChild(link);
    } else {
      svg.appendChild(g);
    }
  }

  function drawMonitorAgents(svg, agents, positions, stats) {
    (agents || []).forEach((a) => {
      const pos = positions[a.id] || { x: 200, y: 570 };
      const stat = (stats || {})[a.agent_key] || {};
      const health = stat.health || 'idle';
      const g = el('g', {
        class: `ops-node-group ops-monitor status-${health}`,
        transform: `translate(${pos.x},${pos.y})`,
      });
      g.appendChild(el('rect', { x: -72, y: -30, width: 144, height: 60, rx: 10, class: 'ops-monitor-box' }));
      g.appendChild(el('text', { y: -6, class: 'ops-node-title' }, a.label));
      g.appendChild(el('text', { y: 10, class: 'ops-node-sub' }, health.toUpperCase()));
      g.appendChild(el('text', { y: 24, class: 'ops-node-role' }, a.role || ''));
      svg.appendChild(g);
    });
  }

  async function loadAgentHealth() {
    try {
      const r = await fetch('/api/agents/mesh');
      if (!r.ok) return {};
      const data = await r.json();
      const map = {};
      (data.nodes || []).forEach((n) => { map[n.agent_key] = { health: n.health, events_6h: n.events_6h }; });
      return map;
    } catch (e) {
      return {};
    }
  }

  async function render(canvasId, metaEl) {
    const svg = document.getElementById(canvasId);
    if (!svg) return;
    clear(svg);
    const width = 1200;
    const height = 620;
    svg.setAttribute('viewBox', `0 0 ${width} ${height}`);

    try {
      const [topoRes, agentStats] = await Promise.all([
        fetch('/api/ops/topology').then((r) => r.json()),
        loadAgentHealth(),
      ]);
      const flat = flattenTree(topoRes.nodes || []);
      const positions = layoutNodes(flat, width);
      (topoRes.layer_agents || []).forEach((a) => {
        if (!positions[a.id]) positions[a.id] = { x: width / 2, y: 570 };
      });

      drawLayerBands(svg, topoRes.layers, width);
      drawEdges(svg, flat, positions);
      flat.forEach((n) => {
        const pos = positions[n.id];
        if (pos) drawNode(svg, n, pos);
      });
      drawMonitorAgents(svg, topoRes.layer_agents, positions, agentStats);

      if (metaEl) {
        const online = (topoRes.meta && topoRes.meta.services_online) || 0;
        metaEl.textContent =
          'Update: ' + (topoRes.generated_at || '—').substring(0, 19).replace('T', ' ') +
          ' · ' + online + ' services online';
      }
    } catch (e) {
      svg.appendChild(el('text', { x: 40, y: 60, fill: '#ef4444', 'font-size': 14 }, 'Topology laden mislukt: ' + e.message));
    }
  }

  window.OpsTopology = { render };
})();
