@php
$topologia = $topologia ?? [];
$attivitaCorrente = $attivitaCorrente ?? null;

$totaleAttivita = count($topologia);
$totalePv = collect($topologia)->sum(fn ($a) => count($a['punti_vendita'] ?? []));
$totaleCucine = collect($topologia)->sum(fn ($a) => count($a['cucine'] ?? []));
$totaleStampantiMancanti = collect($topologia)->sum(function ($a) {
  return collect($a['cucine'] ?? [])->whereNull('stampante')->count();
});
@endphp

<style>
  .md-hidden {
    display: none !important;
  }
  .md-empty {
    border: 1px dashed var(--bs-border-color);
    border-radius: .75rem;
    padding: 2rem 1rem;
    text-align: center;
    color: var(--bs-secondary-color);
  }
  .md-canvas-shell {
    border: 1px solid var(--bs-border-color);
    border-radius: .8rem;
    background: var(--bs-body-bg);
    overflow: hidden;
  }
  .md-canvas-toolbar {
    border-bottom: 1px solid var(--bs-border-color);
    background: var(--bs-tertiary-bg);
    padding: .55rem .7rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .6rem;
    flex-wrap: wrap;
  }
  .md-viewport {
    position: relative;
    height: 72vh;
    min-height: 520px;
    overflow: hidden;
    cursor: grab;
    user-select: none;
    touch-action: none;
    background:
      radial-gradient(circle at 1px 1px, rgba(var(--bs-primary-rgb), .1) 1px, transparent 0);
    background-size: 24px 24px;
  }
  .md-viewport.is-dragging {
    cursor: grabbing;
  }
  .md-canvas {
    position: absolute;
    top: 0;
    left: 0;
    transform-origin: 0 0;
    min-width: 1400px;
    min-height: 1000px;
    padding: 1.25rem;
  }
  .md-graph-wrap {
    margin-bottom: 3rem;
  }
  .md-graph-title {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    margin-bottom: 1rem;
    padding: .35rem .6rem;
    border-radius: .6rem;
    border: 1px solid var(--bs-border-color);
    background: var(--bs-body-bg);
    font-weight: 600;
  }
  .md-tree ul {
    padding-top: 18px;
    position: relative;
    display: flex;
    justify-content: center;
    margin: 0;
  }
  .md-tree li {
    list-style: none;
    text-align: center;
    position: relative;
    padding: 18px 10px 0 10px;
    min-width: 90px;
  }
  .md-tree li::before,
  .md-tree li::after {
    content: '';
    position: absolute;
    top: 0;
    right: 50%;
    border-top: 1px solid rgba(var(--bs-primary-rgb), .28);
    width: 50%;
    height: 18px;
  }
  .md-tree li::after {
    right: auto;
    left: 50%;
    border-left: 1px solid rgba(var(--bs-primary-rgb), .28);
  }
  .md-tree li:only-child::after,
  .md-tree li:only-child::before {
    display: none;
  }
  .md-tree li:only-child {
    padding-top: 0;
  }
  .md-tree li:first-child::before,
  .md-tree li:last-child::after {
    border: 0;
  }
  .md-tree li:last-child::before {
    border-right: 1px solid rgba(var(--bs-primary-rgb), .28);
    border-radius: 0 5px 0 0;
  }
  .md-tree li:first-child::after {
    border-radius: 5px 0 0 0;
  }
  .md-tree ul ul::before {
    content: '';
    position: absolute;
    top: 0;
    left: 50%;
    border-left: 1px solid rgba(var(--bs-primary-rgb), .28);
    width: 0;
    height: 18px;
  }
  .md-tree ul.md-tree-vertical {
    display: block;
    padding-top: 8px;
    margin-left: 10px;
    border-left: 1px solid rgba(var(--bs-primary-rgb), .28);
  }
  .md-tree ul.md-tree-vertical::before {
    display: none;
  }
  .md-tree ul.md-tree-vertical > li {
    min-width: 0;
    text-align: left;
    padding: 8px 0 0 12px;
    position: relative;
  }
  .md-tree ul.md-tree-vertical > li::before,
  .md-tree ul.md-tree-vertical > li::after {
    content: none;
  }
  .md-tree ul.md-tree-vertical > li .md-node {
    margin-left: 0;
  }
  .md-tree ul.md-tree-vertical > li::after {
    content: '';
    display: block;
    position: absolute;
    left: 0;
    top: 19px;
    width: 12px;
    border-top: 1px solid rgba(var(--bs-primary-rgb), .28);
  }
  .md-node {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: .12rem;
    padding: .34rem .42rem;
    border: 1px solid var(--bs-border-color);
    border-radius: .55rem;
    background: var(--bs-body-bg);
    min-width: 104px;
    max-width: 148px;
    font-size: .72rem;
    line-height: 1.2;
    box-shadow: 0 1px 0 rgba(var(--bs-body-color-rgb), .05);
    transition: transform .12s ease, box-shadow .12s ease;
  }
  .md-node--root {
    min-width: 150px;
    font-size: .78rem;
    font-weight: 600;
  }
  .md-node--ok {
    border-color: rgba(var(--bs-success-rgb), .4);
    background: rgba(var(--bs-success-rgb), .08);
  }
  .md-node--warn {
    border-color: rgba(var(--bs-danger-rgb), .45);
    background: rgba(var(--bs-danger-rgb), .09);
  }
  .md-node-label {
    display: inline-flex;
    align-items: center;
    gap: .2rem;
    font-weight: 600;
    color: var(--bs-body-color);
  }
  .md-node-label i {
    font-size: .92rem;
    line-height: 1;
    opacity: .85;
  }
  .md-node-meta {
    display: inline-flex;
    align-items: center;
    gap: .22rem;
    flex-wrap: wrap;
    color: var(--bs-secondary-color);
    font-size: .66rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: normal;
    max-width: 145px;
  }
  .md-node-meta i {
    font-size: .78rem;
    opacity: .8;
  }
  .md-chip-line {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
  }
  .md-device-acc > summary {
    cursor: pointer;
    list-style: none;
    display: block;
    position: relative;
    padding: .45rem 1.65rem .45rem .55rem;
    border: 1px solid var(--bs-border-color);
    border-radius: .55rem;
    background: var(--bs-body-bg);
    min-width: 240px;
  }
  .md-device-acc > summary::-webkit-details-marker { display: none; }
  .md-device-acc > summary::after {
    content: '▾';
    color: var(--bs-secondary-color);
    transition: transform .15s ease;
    position: absolute;
    top: .55rem;
    right: .5rem;
  }
  .md-device-acc[open] > summary::after { transform: rotate(180deg); }
  .md-device-name {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .35rem;
    font-weight: 600;
  }
  .md-device-icons {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .35rem;
    flex-wrap: nowrap;
    margin-top: .3rem;
    min-width: max-content;
  }
  .md-state-icon {
    width: 1.35rem;
    height: 1.35rem;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: .82rem;
    border: 1px solid var(--bs-border-color);
    background: var(--bs-tertiary-bg);
    color: var(--bs-secondary-color);
  }
  .md-state-icon--ok {
    border-color: rgba(var(--bs-success-rgb), .45);
    background: rgba(var(--bs-success-rgb), .12);
    color: rgb(var(--bs-success-rgb));
  }
  .md-state-icon--bad {
    border-color: rgba(var(--bs-danger-rgb), .45);
    background: rgba(var(--bs-danger-rgb), .12);
    color: rgb(var(--bs-danger-rgb));
  }
  .md-device-body {
    margin-top: .4rem;
    padding: .35rem .55rem .55rem .55rem;
    border-top: 1px dashed var(--bs-border-color);
    border-left: 1px solid var(--bs-border-color);
    border-right: 1px solid var(--bs-border-color);
    border-bottom: 1px solid var(--bs-border-color);
    border-radius: 0 0 .55rem .55rem;
  }
  .md-device-table {
    width: 100%;
    border-collapse: collapse;
    font-size: .79rem;
  }
  .md-device-table th,
  .md-device-table td {
    padding: .3rem .35rem;
    border-bottom: 1px dashed var(--bs-border-color);
    vertical-align: top;
  }
  .md-device-table th {
    width: 180px;
    text-align: left;
    color: var(--bs-secondary-color);
    font-weight: 600;
  }
  .md-device-table tr:last-child th,
  .md-device-table tr:last-child td {
    border-bottom: 0;
  }
  @media (max-width: 991.98px) {
    .md-viewport {
      height: 66vh;
      min-height: 440px;
    }
    .md-canvas-toolbar {
      padding: .45rem .55rem;
      gap: .45rem;
    }
    .md-graph-title {
      font-size: .83rem;
      padding: .28rem .48rem;
      margin-bottom: .7rem;
    }
    .md-node {
      min-width: 96px;
      max-width: 132px;
      font-size: .68rem;
      padding: .28rem .35rem;
    }
    .md-node--root {
      min-width: 136px;
      font-size: .74rem;
    }
    .md-node-meta {
      font-size: .62rem;
      max-width: 124px;
    }
    .md-device-acc > summary {
      min-width: 220px;
      padding-right: 1.55rem;
    }
    .md-state-icon {
      width: 1.2rem;
      height: 1.2rem;
      font-size: .74rem;
    }
    .md-device-table {
      font-size: .75rem;
    }
    .md-device-table th {
      width: 140px;
    }
  }
  @media (max-width: 575.98px) {
    .md-viewport {
      height: 60vh;
      min-height: 380px;
    }
    .md-canvas {
      padding: .85rem;
    }
    .md-node {
      min-width: 90px;
      max-width: 122px;
      font-size: .66rem;
    }
    .md-node--root {
      min-width: 126px;
      font-size: .7rem;
    }
    .md-device-acc > summary {
      min-width: 205px;
    }
    .md-device-table th {
      width: 120px;
    }
  }
</style>

<div class="card mb-3">
  <div class="card-body">
    <div class="row g-3 align-items-end">
      <div class="col-md-6">
        <label class="form-label small mb-1" for="mdTreeSearch">Cerca attività/dispositivo</label>
        <input type="text" class="form-control" id="mdTreeSearch" placeholder="Es. Cassa 1, Cucina, Bar...">
      </div>
      <div class="col-md-3">
        <label class="form-label small mb-1" for="mdTreeHealth">Stato topologia</label>
        <select class="form-select" id="mdTreeHealth">
          <option value="all">Tutte</option>
          <option value="warning">Solo criticità</option>
          <option value="ok">Solo configurate</option>
        </select>
      </div>
      <div class="col-md-3">
        <button type="button" class="btn btn-outline-secondary w-100" id="mdTreeResetFilters">
          <i class="bx bx-refresh me-1"></i> Reset filtri
        </button>
      </div>
    </div>
    <div class="mt-3 d-flex flex-wrap gap-2">
      <span class="badge bg-label-primary">Attività: {{ $totaleAttivita }}</span>
      <span class="badge bg-label-info">Punti vendita: {{ $totalePv }}</span>
      <span class="badge bg-label-secondary">Cucine: {{ $totaleCucine }}</span>
      <span class="badge {{ $totaleStampantiMancanti > 0 ? 'bg-label-danger' : 'bg-label-success' }}">
        Cucine senza stampante: {{ $totaleStampantiMancanti }}
      </span>
    </div>
  </div>
</div>

<div class="md-canvas-shell" id="mdDiagramSection">
  <div class="md-canvas-toolbar">
    <div class="small text-muted">
      Navigazione: trascina per spostarti, rotella per zoom.
    </div>
    <div class="d-flex align-items-center gap-2">
      <button class="btn btn-sm btn-outline-secondary" type="button" id="mdZoomOut"><i class="bx bx-minus"></i></button>
      <span class="badge bg-label-primary" id="mdZoomLabel">100%</span>
      <button class="btn btn-sm btn-outline-secondary" type="button" id="mdZoomIn"><i class="bx bx-plus"></i></button>
      <button class="btn btn-sm btn-outline-secondary" type="button" id="mdZoomReset">Centra</button>
      <button class="btn btn-sm btn-outline-secondary" type="button" id="mdZoomFit">Fit</button>
    </div>
  </div>
  <div class="md-viewport" id="mdViewport">
    <div class="md-canvas" id="mdCanvas">
      @forelse($topologia as $att)
        @php
          $pv = collect($att['punti_vendita'] ?? []);
          $cucine = collect($att['cucine'] ?? []);
          $altri = collect($att['altri'] ?? []);

          $pvCritici = $pv->filter(function ($d) {
            return empty($d['endpoint_id']) || empty($d['stampante_cassa']) || !($d['is_attivo'] ?? false);
          })->count();
          $cucineCritiche = $cucine->filter(fn ($c) => empty($c['stampante']))->count();
          $health = ($pvCritici > 0 || $cucineCritiche > 0 || $pv->isEmpty()) ? 'warning' : 'ok';

          $searchBlob = strtolower(implode(' ', array_filter([
            $att['nome'] ?? '',
            $pv->pluck('nome')->implode(' '),
            $cucine->pluck('nome')->implode(' '),
            $altri->pluck('nome')->implode(' '),
          ])));
        @endphp

        <div class="md-graph-wrap md-tree-activity-wrap md-filter-node" data-search="{{ $searchBlob }}" data-health="{{ $health }}">
          <div class="md-graph-title">
            <i class="bx bx-building-house"></i>
            {{ $att['nome'] }}
            <span class="badge {{ $health === 'ok' ? 'bg-label-success' : 'bg-label-danger' }}">
              {{ $health === 'ok' ? 'OK' : 'Criticità' }}
            </span>
          </div>

          <div class="md-tree">
            <ul>
              <li>
                <div class="md-node md-node--root {{ $health === 'ok' ? 'md-node--ok' : 'md-node--warn' }}" title="Attività: {{ $att['nome'] }} | PV: {{ $pv->count() }} | Cucine: {{ $cucine->count() }}">
                  <span class="md-node-label"><i class="bx bx-building-house"></i>Attività</span>
                  <span class="md-node-meta">{{ $att['nome'] }}</span>
                </div>
                <ul>
                  <li>
                    <div class="md-node {{ $pvCritici > 0 || $pv->isEmpty() ? 'md-node--warn' : 'md-node--ok' }}" title="Punti vendita: {{ $pv->count() }} | Criticità: {{ $pvCritici }}">
                      <span class="md-node-label"><i class="bx bx-store-alt"></i>Punti vendita</span>
                      <span class="md-node-meta"><i class="bx bx-grid-alt"></i>{{ $pv->count() }} nodi</span>
                    </div>
                    <ul>
                      @forelse($pv as $d)
                        @php
                          $hasEndpoint = !empty($d['endpoint_id']);
                          $hasStampanteCassa = !empty($d['stampante_cassa']);
                          $isAttivo = (bool)($d['is_attivo'] ?? false);
                          $cucineCollegate = $d['cucine_collegate'] ?? [];
                          $hasCucineCollegate = !empty($cucineCollegate);
                          $pvWarn = !$hasEndpoint || !$hasStampanteCassa || !$isAttivo;
                        @endphp
                        <li>
                          <details class="md-device-acc {{ $pvWarn ? 'md-node--warn' : 'md-node--ok' }}">
                            <summary>
                              <span class="md-device-name">
                                <i class="bx bx-devices"></i>
                                {{ $d['nome'] }}
                                <small class="text-muted">({{ strtoupper((string)($d['tipo'] ?? 'nd')) }})</small>
                              </span>
                              <span class="md-device-icons">
                                <span class="md-state-icon {{ $isAttivo ? 'md-state-icon--ok' : 'md-state-icon--bad' }}" title="Stato dispositivo">
                                  <i class="bx {{ $isAttivo ? 'bx-power-off' : 'bx-x' }}"></i>
                                </span>
                                <span class="md-state-icon {{ $hasEndpoint ? 'md-state-icon--ok' : 'md-state-icon--bad' }}" title="Endpoint">
                                  <i class="bx bx-link-alt"></i>
                                </span>
                                <span class="md-state-icon {{ $hasStampanteCassa ? 'md-state-icon--ok' : 'md-state-icon--bad' }}" title="Stampante cassa">
                                  <i class="bx bx-printer"></i>
                                </span>
                                <span class="md-state-icon {{ $hasCucineCollegate ? 'md-state-icon--ok' : 'md-state-icon--bad' }}" title="Cucine collegate">
                                  <i class="bx bx-dish"></i>
                                </span>
                              </span>
                            </summary>
                            <div class="md-device-body">
                              <table class="md-device-table">
                                <tr><th>Tipo</th><td>{{ strtoupper((string)($d['tipo'] ?? 'nd')) }}</td></tr>
                                <tr><th>Stato</th><td>{{ $isAttivo ? 'Attivo' : 'Non attivo' }}</td></tr>
                                <tr><th>Endpoint</th><td>{{ $d['endpoint_label'] ?? 'Non associato' }}</td></tr>
                                <tr><th>Stampante cassa</th><td>{{ $d['stampante_cassa']['nome'] ?? 'Non associata' }}</td></tr>
                                <tr><th colspan="2">Cucine collegate</th></tr>
                                @if($hasCucineCollegate)
                                  @foreach($cucineCollegate as $cx)
                                    @php
                                      $stampanteCollegata = $cx['stampante'] ?? null;
                                      $stampanteEndpoint = $stampanteCollegata['endpoint_label'] ?? null;
                                      $stampanteLabel = $stampanteCollegata['nome'] ?? 'Stampante assente';
                                      if ($stampanteEndpoint) {
                                        $stampanteLabel .= ' · EP: ' . $stampanteEndpoint;
                                      }
                                    @endphp
                                    <tr>
                                      <td>{{ $cx['nome'] }}</td>
                                      <td>{{ $stampanteLabel }}</td>
                                    </tr>
                                  @endforeach
                                @else
                                  <tr>
                                    <td colspan="2">Nessuna</td>
                                  </tr>
                                @endif
                              </table>
                            </div>
                          </details>
                        </li>
                      @empty
                        <li>
                          <div class="md-node md-node--warn" title="Nessun punto vendita configurato per questa attività">
                            <span class="md-node-label"><i class="bx bx-error-circle"></i>Nessun PV</span>
                            <span class="md-node-meta"><i class="bx bx-unlink"></i>configurazione assente</span>
                          </div>
                        </li>
                      @endforelse
                    </ul>
                  </li>

                  <li>
                    <div class="md-node {{ $cucineCritiche > 0 || $cucine->isEmpty() ? 'md-node--warn' : 'md-node--ok' }}" title="Cucine: {{ $cucine->count() }} | Criticità: {{ $cucineCritiche }}">
                      <span class="md-node-label"><i class="bx bx-dish"></i>Cucine</span>
                      <span class="md-node-meta"><i class="bx bx-grid-alt"></i>{{ $cucine->count() }} nodi</span>
                    </div>
                    <ul>
                      @forelse($cucine as $c)
                        @php
                          $hasSt = !empty($c['stampante']);
                          $piattiCount = (int)($c['piatti_count'] ?? 0);
                          $variantiCount = (int)($c['varianti_count'] ?? 0);
                          $cucinaWarn = !$hasSt;
                        @endphp
                        <li>
                          <details class="md-device-acc {{ $cucinaWarn ? 'md-node--warn' : 'md-node--ok' }}">
                            <summary>
                              <span class="md-device-name">
                                <i class="bx bx-dish"></i>
                                {{ $c['nome'] }}
                                <small class="text-muted">(CUCINA)</small>
                              </span>
                              <span class="md-device-icons">
                                <span class="md-state-icon {{ $hasSt ? 'md-state-icon--ok' : 'md-state-icon--bad' }}" title="Stampante cucina">
                                  <i class="bx bx-printer"></i>
                                </span>
                                <span class="md-state-icon {{ $piattiCount > 0 ? 'md-state-icon--ok' : 'md-state-icon--bad' }}" title="Piatti cucina">
                                  <i class="bx bx-dish"></i>
                                </span>
                                <span class="md-state-icon {{ $variantiCount > 0 ? 'md-state-icon--ok' : 'md-state-icon--bad' }}" title="Varianti cucina">
                                  <i class="bx bx-slider-alt"></i>
                                </span>
                              </span>
                            </summary>
                            <div class="md-device-body">
                              <table class="md-device-table">
                                <tr><th>Stampante</th><td>{{ $c['stampante']['nome'] ?? 'Non associata' }}</td></tr>
                                <tr><th>Count piatti</th><td>{{ $piattiCount }}</td></tr>
                                <tr><th>Count varianti</th><td>{{ $variantiCount }}</td></tr>
                              </table>
                            </div>
                          </details>
                        </li>
                      @empty
                        <li>
                          <div class="md-node md-node--warn" title="Nessuna cucina configurata per questa attività">
                            <span class="md-node-label"><i class="bx bx-error-circle"></i>Nessuna cucina</span>
                            <span class="md-node-meta"><i class="bx bx-unlink"></i>configurazione assente</span>
                          </div>
                        </li>
                      @endforelse
                    </ul>
                  </li>

                  @if($altri->isNotEmpty())
                    <li>
                      <div class="md-node md-node--ok" title="Altri dispositivi: {{ $altri->count() }}">
                        <span class="md-node-label"><i class="bx bx-chip"></i>Altri dispositivi</span>
                        <span class="md-node-meta"><i class="bx bx-grid-alt"></i>{{ $altri->count() }} nodi</span>
                      </div>
                      <ul>
                        @foreach($altri as $d)
                          <li>
                            <div class="md-node {{ ($d['is_attivo'] ?? false) ? 'md-node--ok' : 'md-node--warn' }}" title="Dispositivo: {{ $d['nome'] }} | Tipo: {{ strtoupper((string)($d['tipo'] ?? 'nd')) }} | Attivo: {{ ($d['is_attivo'] ?? false) ? 'si' : 'no' }}">
                              <span class="md-node-label"><i class="bx bx-radio-circle-marked"></i>{{ $d['nome'] }}</span>
                              <span class="md-node-meta"><i class="bx bx-chip"></i>{{ strtoupper((string)($d['tipo'] ?? 'nd')) }}</span>
                            </div>
                          </li>
                        @endforeach
                      </ul>
                    </li>
                  @endif
                </ul>
              </li>
            </ul>
          </div>
        </div>
      @empty
        <div class="md-empty">
          <i class="bx bx-devices fs-2 d-block mb-2"></i>
          Nessuna attività/dispositivo disponibile.
        </div>
      @endforelse
    </div>
  </div>
</div>

<div class="md-empty d-none mt-3" id="mdTreeFilteredEmpty">
  <i class="bx bx-filter-alt fs-2 d-block mb-2"></i>
  Nessuna attività corrisponde ai filtri.
</div>

<script type="module">
  const nodes = Array.from(document.querySelectorAll('.md-filter-node'));
  const search = document.getElementById('mdTreeSearch');
  const health = document.getElementById('mdTreeHealth');
  const resetFilters = document.getElementById('mdTreeResetFilters');
  const empty = document.getElementById('mdTreeFilteredEmpty');

  const viewport = document.getElementById('mdViewport');
  const canvas = document.getElementById('mdCanvas');
  const zoomIn = document.getElementById('mdZoomIn');
  const zoomOut = document.getElementById('mdZoomOut');
  const zoomReset = document.getElementById('mdZoomReset');
  const zoomFit = document.getElementById('mdZoomFit');
  const zoomLabel = document.getElementById('mdZoomLabel');

  let timer;
  let scale = 1;
  let tx = 24;
  let ty = 24;
  const minScale = 0.45;
  const maxScale = 1.8;

  function normalize(v) {
    return (v ?? '').toString().trim().toLowerCase();
  }

  function matches(node) {
    const q = normalize(search?.value);
    const h = health?.value ?? 'all';
    if (h !== 'all' && node.dataset.health !== h) return false;
    if (q.length && !normalize(node.dataset.search).includes(q)) return false;
    return true;
  }

  function applyFilters() {
    let visible = 0;
    nodes.forEach((node) => {
      const hide = !matches(node);
      node.classList.toggle('md-hidden', hide);
      if (!hide) visible += 1;
    });
    if (empty) empty.classList.toggle('d-none', visible > 0);
  }

  function clampScale(v) {
    return Math.min(maxScale, Math.max(minScale, v));
  }

  function renderTransform() {
    canvas.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`;
    if (zoomLabel) zoomLabel.textContent = `${Math.round(scale * 100)}%`;
  }

  function centerCanvas() {
    scale = 1;
    tx = 24;
    ty = 24;
    renderTransform();
  }

  function getVisibleBounds() {
    const visibleWraps = nodes.filter((node) => !node.classList.contains('md-hidden'));
    if (!visibleWraps.length) return null;

    let minX = Infinity;
    let minY = Infinity;
    let maxX = -Infinity;
    let maxY = -Infinity;

    visibleWraps.forEach((wrap) => {
      const x = wrap.offsetLeft;
      const y = wrap.offsetTop;
      const w = wrap.offsetWidth;
      const h = wrap.offsetHeight;
      minX = Math.min(minX, x);
      minY = Math.min(minY, y);
      maxX = Math.max(maxX, x + w);
      maxY = Math.max(maxY, y + h);
    });

    return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY };
  }

  function fitToVisible() {
    if (!viewport) return;
    const bounds = getVisibleBounds();
    if (!bounds || bounds.width <= 0 || bounds.height <= 0) {
      centerCanvas();
      return;
    }

    const padding = 40;
    const vw = viewport.clientWidth;
    const vh = viewport.clientHeight;
    const sx = (vw - padding * 2) / bounds.width;
    const sy = (vh - padding * 2) / bounds.height;
    scale = clampScale(Math.min(sx, sy, 1.4));

    tx = padding - bounds.minX * scale;
    ty = padding - bounds.minY * scale;
    renderTransform();
  }

  function focusFirstWarning() {
    if (!viewport) return false;
    const warningNode = document.querySelector('.md-tree-activity-wrap.md-filter-node:not(.md-hidden) .md-node--warn');
    if (!warningNode) return false;

    const nodeRect = warningNode.getBoundingClientRect();
    const vpRect = viewport.getBoundingClientRect();
    const ww = nodeRect.width;
    const wh = nodeRect.height;
    const wx = (nodeRect.left - vpRect.left - tx) / scale;
    const wy = (nodeRect.top - vpRect.top - ty) / scale;

    tx = (viewport.clientWidth / 2) - ((wx + ww / 2) * scale);
    ty = (viewport.clientHeight / 2) - ((wy + wh / 2) * scale);
    renderTransform();
    return true;
  }

  function zoomBy(step) {
    scale = clampScale(scale + step);
    renderTransform();
  }

  let dragging = false;
  let startX = 0;
  let startY = 0;
  let baseTx = 0;
  let baseTy = 0;
  let isPinching = false;
  let pinchStartDistance = 0;
  let pinchStartScale = 1;
  let pinchStartTx = 0;
  let pinchStartWorldX = 0;
  let pinchStartWorldY = 0;
  const activePointers = new Map();

  function getDistance(p1, p2) {
    const dx = p1.x - p2.x;
    const dy = p1.y - p2.y;
    return Math.hypot(dx, dy);
  }

  function getMidpoint(p1, p2) {
    return {
      x: (p1.x + p2.x) / 2,
      y: (p1.y + p2.y) / 2,
    };
  }

  function beginSinglePointerDrag(pointer) {
    dragging = true;
    isPinching = false;
    viewport?.classList.add('is-dragging');
    startX = pointer.x;
    startY = pointer.y;
    baseTx = tx;
    baseTy = ty;
  }

  function beginPinch() {
    const pointers = Array.from(activePointers.values());
    if (pointers.length < 2) return;
    dragging = false;
    isPinching = true;
    viewport?.classList.add('is-dragging');

    const p1 = pointers[0];
    const p2 = pointers[1];
    const midpoint = getMidpoint(p1, p2);
    pinchStartDistance = Math.max(1, getDistance(p1, p2));
    pinchStartScale = scale;
    pinchStartTx = tx;
    pinchStartWorldX = (midpoint.x - pinchStartTx) / pinchStartScale;
    pinchStartWorldY = (midpoint.y - ty) / pinchStartScale;
  }

  function handlePointerEnd(pointerId) {
    activePointers.delete(pointerId);

    if (activePointers.size === 0) {
      dragging = false;
      isPinching = false;
      viewport?.classList.remove('is-dragging');
      return;
    }

    if (activePointers.size === 1) {
      const remaining = Array.from(activePointers.values())[0];
      beginSinglePointerDrag(remaining);
    }
  }

  function isInteractiveTarget(target) {
    if (!(target instanceof Element)) return false;
    return Boolean(
      target.closest('summary, button, input, select, textarea, a, .md-device-body, .md-device-table')
    );
  }

  viewport?.addEventListener('pointerdown', (event) => {
    if (isInteractiveTarget(event.target)) return;
    if (event.pointerType === 'mouse' && event.button !== 0) return;
    activePointers.set(event.pointerId, { x: event.clientX, y: event.clientY });
    viewport.setPointerCapture?.(event.pointerId);

    if (activePointers.size === 1) {
      beginSinglePointerDrag({ x: event.clientX, y: event.clientY });
    } else if (activePointers.size >= 2) {
      beginPinch();
    }
  });

  viewport?.addEventListener('pointermove', (event) => {
    if (!activePointers.has(event.pointerId)) return;
    activePointers.set(event.pointerId, { x: event.clientX, y: event.clientY });

    if (isPinching && activePointers.size >= 2) {
      const pointers = Array.from(activePointers.values());
      const p1 = pointers[0];
      const p2 = pointers[1];
      const midpoint = getMidpoint(p1, p2);
      const distance = Math.max(1, getDistance(p1, p2));
      scale = clampScale(pinchStartScale * (distance / pinchStartDistance));
      tx = midpoint.x - (pinchStartWorldX * scale);
      ty = midpoint.y - (pinchStartWorldY * scale);
      renderTransform();
      return;
    }

    if (dragging && activePointers.size === 1) {
      tx = baseTx + (event.clientX - startX);
      ty = baseTy + (event.clientY - startY);
      renderTransform();
    }
  });

  viewport?.addEventListener('pointerup', (event) => {
    handlePointerEnd(event.pointerId);
    viewport.releasePointerCapture?.(event.pointerId);
  });

  viewport?.addEventListener('pointercancel', (event) => {
    handlePointerEnd(event.pointerId);
    viewport.releasePointerCapture?.(event.pointerId);
  });

  viewport?.addEventListener('wheel', (event) => {
    event.preventDefault();
    const delta = event.deltaY < 0 ? 0.08 : -0.08;
    scale = clampScale(scale + delta);
    renderTransform();
  }, { passive: false });

  zoomIn?.addEventListener('click', () => zoomBy(0.1));
  zoomOut?.addEventListener('click', () => zoomBy(-0.1));
  zoomReset?.addEventListener('click', centerCanvas);
  zoomFit?.addEventListener('click', fitToVisible);

  search?.addEventListener('input', () => {
    clearTimeout(timer);
    timer = setTimeout(applyFilters, 150);
  });
  health?.addEventListener('change', applyFilters);
  resetFilters?.addEventListener('click', () => {
    if (search) search.value = '';
    if (health) health.value = 'all';
    applyFilters();
    fitToVisible();
    focusFirstWarning();
  });

  function initTreeView() {
    applyFilters();
    fitToVisible();
    if (!focusFirstWarning()) {
      centerCanvas();
    }
  }

  window.mdInitTreeView = initTreeView;

  const treePane = document.getElementById('mdPaneTree');
  if (treePane?.classList.contains('active') && treePane.classList.contains('show')) {
    initTreeView();
  } else {
    document.getElementById('mdViewTabs')?.addEventListener('shown.bs.tab', (event) => {
      if (event.target?.dataset?.bsTarget === '#mdPaneTree') {
        initTreeView();
      }
    });
  }
</script>
