<template>
  <q-page padding>
    <!-- Tabs -->
    <div class="row items-center q-mb-md">
      <q-tabs v-model="activeTab" dense no-caps class="text-grey-7" active-color="primary" indicator-color="primary">
        <q-tab name="gpon" icon="lan" label="Réseau GPON" />
        <q-tab name="map" icon="map" label="Carte réseau" />
        <q-tab name="topology" icon="hub" label="Topologie" />
        <q-tab name="analysis" icon="psychology" label="Analyse IA" />
      </q-tabs>
      <q-space />
      <template v-if="activeTab === 'gpon'">
        <q-btn unelevated dense no-caps icon="add" label="Ajouter OLT" color="primary" @click="showOltModal = true" />
        <div class="text-caption text-grey-6 q-ml-md">{{ totalLocations }} locations fibre</div>
      </template>
      <template v-if="activeTab === 'map'">
        <q-btn flat dense no-caps icon="refresh" label="Rafraîchir" color="grey-7" :loading="mapLoading" @click="loadNetworkMap" />
      </template>
      <template v-if="activeTab === 'topology'">
        <q-btn flat dense no-caps icon="refresh" label="Rafraîchir" color="grey-7" :loading="topoLoading" @click="loadTopology" />
      </template>
      <template v-if="activeTab === 'analysis'">
        <q-btn unelevated dense no-caps icon="play_arrow" label="Lancer analyse" color="primary" :loading="analysisLoading" @click="runAnalysis" />
      </template>
    </div>

    <!-- ═══════════ TAB: GPON ═══════════ -->
    <div v-show="activeTab === 'gpon'">

    <!-- Pause polling (test phase) -->
    <div class="poller-card q-mb-md">
      <div class="row items-center no-wrap q-gutter-md">
        <q-icon name="bolt" size="22px"
          :color="pollerState.device.paused || pollerState.olt.paused ? 'negative' : 'positive'" />
        <div class="col">
          <div class="text-weight-medium">Pollers réseau</div>
          <div class="text-caption text-grey-7">
            Désactive temporairement les sondes GenieACS + SNMP pendant la phase de test.
            <span v-if="pollerState.device.paused || pollerState.olt.paused" class="text-negative">
              · Certains pollers sont en pause.
            </span>
          </div>
        </div>
        <q-btn flat dense size="sm" icon="refresh" :loading="pollerLoading" @click="fetchPollerState" />
      </div>

      <q-separator spaced />

      <div class="row q-col-gutter-md">
        <div class="col-12 col-sm-6">
          <div class="row items-center no-wrap">
            <div class="col">
              <div class="text-body2 text-weight-medium row items-center q-gutter-xs">
                <span>Poller ONT (GenieACS)</span>
                <q-chip
                  dense square size="sm"
                  :color="pollerState.device.paused ? 'negative' : 'positive'"
                  text-color="white"
                  :icon="pollerState.device.paused ? 'pause' : 'check'"
                  :label="pollerState.device.paused ? 'En pause' : 'Actif'"
                  class="poller-chip"
                />
              </div>
              <div class="text-caption text-grey-6">
                {{ pollerState.device.paused ? 'Sondes GenieACS arrêtées' : 'Sonde ~6000 appareils / 5 min' }}
                <template v-if="pollerState.device.lastChange">
                  · dernier changement {{ formatPollerTs(pollerState.device.lastChange) }}
                </template>
              </div>
            </div>
            <q-toggle
              :model-value="!pollerState.device.paused"
              color="positive"
              :disable="pollerLoading"
              @update:model-value="(v) => setPollerPaused('device', !v)"
            />
          </div>
        </div>

        <div class="col-12 col-sm-6">
          <div class="row items-center no-wrap">
            <div class="col">
              <div class="text-body2 text-weight-medium row items-center q-gutter-xs">
                <span>Poller OLT (SNMP)</span>
                <q-chip
                  dense square size="sm"
                  :color="pollerState.olt.paused ? 'negative' : 'positive'"
                  text-color="white"
                  :icon="pollerState.olt.paused ? 'pause' : 'check'"
                  :label="pollerState.olt.paused ? 'En pause' : 'Actif'"
                  class="poller-chip"
                />
              </div>
              <div class="text-caption text-grey-6">
                {{ pollerState.olt.paused ? 'Sondes SNMP arrêtées' : '4 OLT / 5 min' }}
                <template v-if="pollerState.olt.lastChange">
                  · dernier changement {{ formatPollerTs(pollerState.olt.lastChange) }}
                </template>
              </div>
            </div>
            <q-toggle
              :model-value="!pollerState.olt.paused"
              color="positive"
              :disable="pollerLoading"
              @update:model-value="(v) => setPollerPaused('olt', !v)"
            />
          </div>
        </div>
      </div>
    </div>

    <!-- Search bar (GPON tab) -->
    <div class="row items-center q-mb-md q-col-gutter-sm">
      <div class="col-12 col-md-4">
        <q-input
          v-model="search" dense outlined placeholder="OLT IP, slot, port, client, serial..."
          class="ops-search" @update:model-value="onSearchInput"
        >
          <template #prepend><q-icon name="search" /></template>
          <template #append>
            <q-spinner v-if="loading" size="16px" color="grey-5" />
            <q-icon v-else-if="search" name="close" class="cursor-pointer" @click="clearSearch" />
          </template>
        </q-input>
      </div>
    </div>

    <!-- OLT Tree -->
    <div v-if="loading && !olts.length" class="text-center q-pa-xl">
      <q-spinner size="32px" color="primary" />
      <div class="text-grey-6 q-mt-sm">Chargement du réseau...</div>
    </div>

    <div v-else class="net-tree">
      <div v-for="olt in filteredOlts" :key="olt.ip" class="net-olt">
        <!-- OLT Header -->
        <div class="net-olt-header" @click="toggleOlt(olt.ip)">
          <q-icon :name="expandedOlts[olt.ip] ? 'expand_more' : 'chevron_right'" size="20px" class="q-mr-xs" />
          <span class="net-olt-icon">&#x1F4E1;</span>
          <span class="net-olt-name">{{ olt.name || olt.ip }}</span>
          <span class="net-olt-ip">{{ olt.ip }}</span>
          <span v-if="olt.address" class="net-olt-addr">{{ olt.address }}</span>
          <span class="net-olt-count">{{ olt.locationCount }} clients</span>
          <span v-if="snmpStats[olt.ip] && !snmpStats[olt.ip].lastError" class="net-snmp-badge net-snmp-ok">
            <span class="net-snmp-dot net-snmp-dot--green"></span>{{ snmpStats[olt.ip].onuCount }} ONUs
          </span>
          <span v-else-if="snmpStats[olt.ip] && snmpStats[olt.ip].lastError" class="net-snmp-badge net-snmp-err">
            <span class="net-snmp-dot net-snmp-dot--red"></span>SNMP inaccessible
          </span>
          <span v-else class="net-snmp-badge net-snmp-none">
            <span class="net-snmp-dot net-snmp-dot--grey"></span>Non configur&eacute;
          </span>
          <span class="net-olt-slots">{{ olt.slotCount }} slots</span>
          <q-btn flat dense round size="xs" icon="edit" class="net-olt-edit-btn" @click.stop="startEditOlt(olt)" />
        </div>

        <!-- Slots -->
        <div v-if="expandedOlts[olt.ip]" class="net-slots">
          <div v-for="slot in olt.slots" :key="slot.num" class="net-slot">
            <div class="net-slot-header" @click="toggleSlot(olt.ip, slot.num)">
              <q-icon :name="expandedSlots[olt.ip + '/' + slot.num] ? 'expand_more' : 'chevron_right'" size="18px" class="q-mr-xs" />
              <span class="net-slot-label">Slot {{ slot.num }}</span>
              <span class="net-slot-count">{{ slot.locationCount }} clients &middot; {{ slot.portCount }} ports</span>
            </div>

            <!-- Ports -->
            <div v-if="expandedSlots[olt.ip + '/' + slot.num]" class="net-ports">
              <div v-for="port in slot.ports" :key="port.num" class="net-port">
                <div class="net-port-header" @click="togglePort(olt.ip, slot.num, port.num)">
                  <q-icon :name="expandedPorts[olt.ip + '/' + slot.num + '/' + port.num] ? 'expand_more' : 'chevron_right'" size="16px" class="q-mr-xs" />
                  <span class="net-port-label">Port {{ port.num }}</span>
                  <span class="net-port-count">{{ port.locations.length }} ONT</span>
                  <q-btn flat dense round size="xs" icon="add" class="net-port-add-btn"
                    @click.stop="startAddLocation(olt, slot.num, port.num)" title="Ajouter une adresse" />
                </div>

                <!-- Locations (clients) -->
                <div v-if="expandedPorts[olt.ip + '/' + slot.num + '/' + port.num]" class="net-locations">
                  <div
                    v-for="loc in port.locations" :key="loc.name"
                    class="net-location"
                    :class="{ 'net-location-editing': editingLoc === loc.name }"
                  >
                    <div class="net-loc-main">
                      <span class="net-loc-ont">ONT:{{ loc.ont_id }}</span>
                      <span class="net-loc-customer" @click="goToClient(loc.customer)">{{ loc.location_name || loc.customer }}</span>
                      <span class="net-loc-address">{{ loc.address_line }}</span>
                      <span v-if="loc.city" class="net-loc-city">{{ loc.city }}</span>
                      <span v-if="loc.ont_serial" class="net-loc-serial">{{ loc.ont_serial }}</span>
                      <q-btn flat dense round size="xs" icon="edit" class="net-loc-edit-btn" @click.stop="startEdit(loc)" />
                    </div>

                    <!-- Inline edit location -->
                    <div v-if="editingLoc === loc.name" class="net-loc-edit" @click.stop>
                      <div class="net-edit-grid">
                        <div class="net-edit-section">
                          <div class="net-edit-section-title">Adresse</div>
                          <div class="net-edit-row">
                            <label>Adresse</label>
                            <q-input v-model="editForm.address_line" dense outlined class="net-edit-field-lg" />
                          </div>
                          <div class="net-edit-row">
                            <label>Ville</label>
                            <q-input v-model="editForm.city" dense outlined class="net-edit-field" />
                          </div>
                          <div class="net-edit-row">
                            <label>Code postal</label>
                            <q-input v-model="editForm.postal_code" dense outlined class="net-edit-field-sm" />
                          </div>
                        </div>
                        <div class="net-edit-section">
                          <div class="net-edit-section-title">Connexion OLT</div>
                          <div class="net-edit-row">
                            <label>OLT</label>
                            <q-select v-model="editForm.olt_ip" :options="oltIpOptions" dense outlined emit-value map-options class="net-edit-field" />
                          </div>
                          <div class="net-edit-row">
                            <label>Slot</label>
                            <q-input v-model.number="editForm.slot" type="number" dense outlined class="net-edit-field-sm" />
                          </div>
                          <div class="net-edit-row">
                            <label>Port</label>
                            <q-input v-model.number="editForm.port" type="number" dense outlined class="net-edit-field-sm" />
                          </div>
                          <div class="net-edit-row">
                            <label>ONT ID</label>
                            <q-input v-model.number="editForm.ont_id" type="number" dense outlined class="net-edit-field-sm" />
                          </div>
                          <div class="net-edit-row">
                            <label>Serial</label>
                            <q-input v-model="editForm.ont_serial" dense outlined class="net-edit-field" />
                          </div>
                        </div>
                      </div>
                      <div class="net-edit-actions">
                        <q-btn flat dense no-caps label="Annuler" color="grey-7" @click="cancelEdit" />
                        <q-btn unelevated dense no-caps label="Sauvegarder" color="primary" :loading="saving" @click="saveEdit" />
                      </div>
                    </div>
                  </div>

                  <!-- Add location inline form -->
                  <div v-if="addingAt === olt.ip + '/' + slot.num + '/' + port.num" class="net-location net-location-editing">
                    <div class="net-loc-edit" @click.stop>
                      <div class="net-edit-section-title" style="width:100%">Nouvelle adresse sur {{ olt.name || olt.ip }} &mdash; Slot {{ slot.num }} / Port {{ port.num }}</div>
                      <div class="net-edit-grid">
                        <div class="net-edit-section">
                          <div class="net-edit-row">
                            <label>Client</label>
                            <q-input v-model="addForm.customerSearch" dense outlined placeholder="Rechercher client..."
                              class="net-edit-field-lg" @update:model-value="searchCustomers" />
                          </div>
                          <div v-if="customerResults.length" class="net-customer-results">
                            <div v-for="c in customerResults" :key="c.name" class="net-customer-result"
                              :class="{ active: addForm.customer === c.name }" @click="selectCustomer(c)">
                              {{ c.customer_name }} <span class="text-grey-5">{{ c.name }}</span>
                            </div>
                          </div>
                          <div class="net-edit-row">
                            <label>Adresse</label>
                            <q-input v-model="addForm.address_line" dense outlined class="net-edit-field-lg" />
                          </div>
                          <div class="net-edit-row">
                            <label>Ville</label>
                            <q-input v-model="addForm.city" dense outlined class="net-edit-field" />
                          </div>
                          <div class="net-edit-row">
                            <label>Code postal</label>
                            <q-input v-model="addForm.postal_code" dense outlined class="net-edit-field-sm" />
                          </div>
                        </div>
                        <div class="net-edit-section">
                          <div class="net-edit-row">
                            <label>ONT ID</label>
                            <q-input v-model.number="addForm.ont_id" type="number" dense outlined class="net-edit-field-sm" />
                          </div>
                          <div class="net-edit-row">
                            <label>Serial</label>
                            <q-input v-model="addForm.ont_serial" dense outlined class="net-edit-field" />
                          </div>
                        </div>
                      </div>
                      <div class="net-edit-actions">
                        <q-btn flat dense no-caps label="Annuler" color="grey-7" @click="addingAt = null" />
                        <q-btn unelevated dense no-caps label="Ajouter" color="primary" :loading="saving"
                          :disable="!addForm.customer" @click="saveNewLocation" />
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <div v-if="!filteredOlts.length && !loading" class="text-center q-pa-xl text-grey-5">
        {{ search ? 'Aucun résultat pour cette recherche' : 'Aucune donnée OLT' }}
      </div>
    </div>

    </div><!-- end GPON tab -->

    <!-- ═══════════ TAB: CARTE RÉSEAU ═══════════ -->
    <div v-show="activeTab === 'map'">
      <div v-if="mapLoading && !mapData" class="text-center q-pa-xl">
        <q-spinner size="32px" color="primary" />
        <div class="text-grey-6 q-mt-sm">Chargement de la carte réseau...</div>
      </div>

      <div v-else-if="mapData" class="netmap-container">
        <!-- Summary bar -->
        <div class="netmap-summary row q-col-gutter-sm q-mb-sm">
          <div class="col-auto"><q-chip dense color="green-1" text-color="green-9" icon="location_on">{{ mapData.summary.totalSites }} sites</q-chip></div>
          <div class="col-auto"><q-chip dense color="green-1" text-color="green-9" icon="check_circle">{{ mapData.summary.sitesUp }} UP</q-chip></div>
          <div class="col-auto" v-if="mapData.summary.sitesDown"><q-chip dense color="red-1" text-color="red-9" icon="error">{{ mapData.summary.sitesDown }} dégradé</q-chip></div>
          <div class="col-auto"><q-chip dense color="amber-1" text-color="amber-9" icon="router">{{ mapData.summary.totalOlts }} OLTs</q-chip></div>
          <div class="col-auto" v-if="mapData.summary.totalOnus"><q-chip dense color="blue-1" text-color="blue-9" icon="people">{{ mapData.summary.totalOnusOnline }}/{{ mapData.summary.totalOnus }} ONUs</q-chip></div>
          <div class="col-auto"><q-chip dense color="grey-2" text-color="grey-8" icon="cable">{{ mapData.summary.totalLinks }} liens fibre</q-chip></div>
        </div>

        <!-- Cytoscape Canvas -->
        <div ref="cyContainer" class="netmap-canvas"></div>

        <!-- Selected site detail panel -->
        <div v-if="mapSelectedSite" class="netmap-detail q-mt-sm">
          <q-card flat bordered>
            <q-card-section class="q-pa-sm">
              <div class="row items-center q-mb-xs">
                <div class="text-subtitle1 text-weight-bold">{{ mapSelectedSite.name }}</div>
                <q-space />
                <q-chip dense :color="mapSelectedSite.status === 'up' ? 'green' : mapSelectedSite.status === 'degraded' ? 'red' : 'grey'" text-color="white" :label="mapSelectedSite.status" />
                <q-btn flat dense round icon="close" size="sm" @click="mapSelectedSite = null" />
              </div>
              <div class="text-caption text-grey-6">{{ mapSelectedSite.city }} — {{ mapSelectedSite.address }}</div>

              <!-- Devices at this site -->
              <div class="q-mt-sm">
                <div v-for="dev in mapSelectedSite.devices" :key="dev.ip" class="netmap-device row items-center q-py-xs"
                  :class="{ 'netmap-device--down': dev.kuma?.status === 0 }">
                  <q-icon :name="dev.type === 'router' ? 'router' : dev.type === 'olt' ? 'device_hub' : 'dns'" size="16px" class="q-mr-xs" />
                  <span class="text-weight-medium" style="font-size:12px">{{ dev.hostname }}</span>
                  <span class="text-grey-5 q-mx-xs">|</span>
                  <code style="font-size:11px">{{ dev.ip }}</code>
                  <span class="text-grey-5 q-mx-xs">|</span>
                  <span style="font-size:11px" class="text-grey-6">{{ dev.make }} {{ dev.model || '' }}</span>
                  <q-space />
                  <q-badge v-if="dev.kuma" :color="dev.kuma.status === 1 ? 'green' : dev.kuma.status === 0 ? 'red' : 'orange'" :label="dev.kuma.status === 1 ? 'UP' : 'DOWN'" />
                  <q-badge v-else color="grey-4" text-color="grey-7" label="No Kuma" />
                  <q-badge v-if="dev.olt" color="blue-1" text-color="blue-9" :label="`${dev.olt.onlineCount}/${dev.olt.onuCount} ONUs`" class="q-ml-xs" />
                  <q-badge v-if="dev.status === 'planned'" color="amber-1" text-color="amber-9" label="Planifié" class="q-ml-xs" />
                </div>
              </div>

              <!-- Connected links -->
              <div class="q-mt-sm" v-if="mapSelectedSiteLinks.length">
                <div class="text-caption text-weight-bold text-grey-7 q-mb-xs">Liens fibre :</div>
                <div v-for="link in mapSelectedSiteLinks" :key="link.id" class="row items-center q-py-xs" style="font-size:11px">
                  <q-icon name="cable" size="14px" class="q-mr-xs text-grey-5" />
                  <span>→ {{ link.from === mapSelectedSiteId ? link.to : link.from }}</span>
                  <span class="text-grey-5 q-mx-xs">|</span>
                  <span v-if="link.speed_gbps" class="text-weight-medium">{{ link.speed_gbps }}G</span>
                  <span v-if="link.distance_km" class="text-grey-6 q-ml-xs">{{ link.distance_km }}km</span>
                  <span v-if="link.note" class="text-grey-5 q-ml-xs">{{ link.note }}</span>
                </div>
              </div>
            </q-card-section>
          </q-card>
        </div>
      </div>

      <div v-else class="text-center q-pa-xl text-grey-5">
        Cliquez sur Rafraîchir pour charger la carte réseau
      </div>
    </div>

    <!-- ═══════════ TAB: TOPOLOGY ═══════════ -->
    <div v-show="activeTab === 'topology'">
      <div v-if="topoLoading && !topoData" class="text-center q-pa-xl">
        <q-spinner size="32px" color="primary" />
        <div class="text-grey-6 q-mt-sm">Découverte de la topologie réseau...</div>
      </div>

      <div v-else-if="topoData" class="topo-container">
        <!-- Summary Cards -->
        <div class="topo-summary row q-col-gutter-sm q-mb-md">
          <div class="col-auto" v-for="(count, type) in topoData.topology.summary" :key="type">
            <q-card flat bordered class="topo-summary-card" :class="'topo-type-' + type">
              <q-card-section class="q-pa-sm text-center">
                <div class="topo-summary-count">{{ count }}</div>
                <div class="topo-summary-label">{{ topoTypeLabel(type) }}</div>
              </q-card-section>
            </q-card>
          </div>
          <div class="col-auto">
            <q-card flat bordered class="topo-summary-card">
              <q-card-section class="q-pa-sm text-center">
                <div class="topo-summary-count">{{ topoData.topology.links.length }}</div>
                <div class="topo-summary-label">Liens</div>
              </q-card-section>
            </q-card>
          </div>
        </div>

        <!-- Network Hierarchy Visualization -->
        <div class="topo-hierarchy">
          <!-- Level 0: Core Switches -->
          <div class="topo-level" v-if="topoByLevel[0]?.length">
            <div class="topo-level-label">CORE</div>
            <div class="topo-level-devices">
              <div v-for="dev in topoByLevel[0]" :key="dev.host" class="topo-device topo-device--core"
                :class="devClass(dev)"
                @click="selectDevice(dev)">
                <div class="topo-kuma-dot" :class="kumaClass(dev)" :title="kumaTitle(dev)"></div>
                <q-icon :name="dev.icon" size="20px" />
                <div class="topo-device-host">{{ dev.host }}</div>
                <div v-if="dev.kuma" class="topo-kuma-label">{{ dev.kuma.name }}</div>
                <div class="topo-device-health">
                  <div class="topo-health-bar">
                    <div class="topo-health-fill" :style="{ width: (dev.ifSummary?.health || 0) + '%' }"
                      :class="healthClass(dev.ifSummary?.health)"></div>
                  </div>
                  <span class="topo-health-text">{{ dev.ifSummary?.health ?? '?' }}%</span>
                </div>
                <div class="topo-device-stats">
                  {{ dev.ifSummary?.up || 0 }}<span class="text-green">▲</span>
                  {{ dev.ifSummary?.down || 0 }}<span class="text-red">▼</span>
                  / {{ dev.ifSummary?.total || 0 }}
                </div>
              </div>
            </div>
          </div>

          <!-- Level 1: Edge Routers + Public Infra -->
          <div class="topo-level" v-if="topoByLevel[1]?.length">
            <div class="topo-level-label">EDGE / TRANSIT</div>
            <div class="topo-level-devices">
              <div v-for="dev in topoByLevel[1]" :key="dev.host" class="topo-device topo-device--edge"
                :class="devClass(dev)"
                @click="selectDevice(dev)">
                <div class="topo-kuma-dot" :class="kumaClass(dev)" :title="kumaTitle(dev)"></div>
                <q-icon :name="dev.icon" size="18px" />
                <div class="topo-device-host">{{ dev.host }}</div>
                <div class="topo-device-type">{{ dev.label }}</div>
                <div class="topo-device-health">
                  <div class="topo-health-bar">
                    <div class="topo-health-fill" :style="{ width: (dev.ifSummary?.health || 0) + '%' }"
                      :class="healthClass(dev.ifSummary?.health)"></div>
                  </div>
                  <span class="topo-health-text">{{ dev.ifSummary?.health ?? '?' }}%</span>
                </div>
              </div>
            </div>
          </div>

          <!-- Level 2: OLT Gateways -->
          <div class="topo-level" v-if="topoByLevel[2]?.length">
            <div class="topo-level-label">OLT GATEWAYS</div>
            <div class="topo-level-devices">
              <div v-for="dev in topoByLevel[2]" :key="dev.host" class="topo-device topo-device--olt"
                :class="devClass(dev)"
                @click="selectDevice(dev)">
                <div class="topo-kuma-dot" :class="kumaClass(dev)" :title="kumaTitle(dev)"></div>
                <q-icon :name="dev.icon" size="18px" />
                <div class="topo-device-host">{{ dev.host }}</div>
                <div class="topo-device-health">
                  <div class="topo-health-bar">
                    <div class="topo-health-fill" :style="{ width: (dev.ifSummary?.health || 0) + '%' }"
                      :class="healthClass(dev.ifSummary?.health)"></div>
                  </div>
                  <span class="topo-health-text">{{ dev.ifSummary?.health ?? '?' }}%</span>
                </div>
              </div>
            </div>
          </div>

          <!-- OLT chains (from dependency map) -->
          <div class="topo-level" v-if="oltChains.length">
            <div class="topo-level-label">OLTs (GPON)</div>
            <div class="topo-level-devices">
              <div v-for="chain in oltChains" :key="chain.leaf" class="topo-device topo-device--olt-leaf"
                :class="{ 'topo-device--danger': chain.path[chain.path.length-1]?.health === 0 }">
                <q-icon name="settings_input_antenna" size="16px" />
                <div class="topo-device-host">{{ chain.oltName || chain.leaf }}</div>
                <div v-if="chain.onuCount" class="topo-device-onus">
                  {{ chain.onlineCount }}/{{ chain.onuCount }} ONUs
                  <q-icon :name="chain.onlineCount === chain.onuCount ? 'check_circle' : 'warning'" size="12px"
                    :color="chain.onlineCount === chain.onuCount ? 'green' : 'orange'" />
                </div>
              </div>
            </div>
          </div>
        </div>

        <!-- Dependency Chains -->
        <div class="topo-chains q-mt-lg" v-if="topoData.chains?.length">
          <div class="text-subtitle2 q-mb-sm">Chaînes de dépendance</div>
          <div v-for="(chain, i) in topoData.chains.slice(0, 20)" :key="i" class="topo-chain">
            <template v-for="(node, ni) in chain.path" :key="ni">
              <span class="topo-chain-node" :class="'topo-chain-' + node.type"
                :title="`${node.host} — ${node.label} — Santé: ${node.health}%`">
                <span class="topo-chain-health" :class="healthClass(node.health)">●</span>
                {{ node.host }}
                <span v-if="node.onuCount" class="topo-chain-onus">({{ node.onlineCount }}/{{ node.onuCount }})</span>
              </span>
              <span v-if="ni < chain.path.length - 1" class="topo-chain-arrow">→</span>
            </template>
          </div>
        </div>

        <!-- Selected device detail -->
        <div v-if="selectedDevice && selectedDeviceData" class="topo-detail q-mt-lg">
          <q-card flat bordered>
            <q-card-section>
              <div class="row items-center q-mb-sm">
                <q-icon :name="selectedDeviceData.icon" size="24px" class="q-mr-sm" />
                <div>
                  <div class="text-h6" style="line-height:1.2">{{ selectedDeviceData.host }}</div>
                  <div class="text-caption text-grey-6">{{ selectedDeviceData.label }} — {{ selectedDeviceData.type }}</div>
                </div>
                <q-space />
                <q-btn flat dense round icon="close" @click="selectedDevice = null" />
              </div>
              <div class="row q-col-gutter-sm q-mb-sm">
                <div class="col-auto">
                  <q-badge color="green" :label="`${selectedDeviceData.ifSummary?.up || 0} up`" />
                </div>
                <div class="col-auto">
                  <q-badge color="red" :label="`${selectedDeviceData.ifSummary?.down || 0} down`" />
                </div>
                <div class="col-auto">
                  <q-badge color="grey" :label="`${selectedDeviceData.ifSummary?.total || 0} total`" />
                </div>
                <div v-if="selectedDeviceData.traffic" class="col-auto">
                  <q-badge color="blue" :label="`↓${selectedDeviceData.traffic.totalInGb}GB ↑${selectedDeviceData.traffic.totalOutGb}GB`" />
                </div>
              </div>
              <!-- Connected links -->
              <div class="text-caption text-grey-6 q-mb-xs">Connexions:</div>
              <div v-for="link in deviceLinks" :key="link.from + link.to" class="topo-detail-link">
                <q-icon :name="link.status === 'up' ? 'link' : link.status === 'down' ? 'link_off' : 'help'" size="14px"
                  :color="link.status === 'up' ? 'green' : link.status === 'down' ? 'red' : 'grey'" />
                {{ link.from === selectedDevice ? link.to : link.from }}
                <span class="text-grey-5">({{ link.type }}, via: {{ link.via }})</span>
                <span v-if="link.oltName" class="text-green-4">{{ link.oltName }} — {{ link.onlineCount }}/{{ link.onuCount }} ONUs</span>
              </div>
              <!-- Interface detail -->
              <div v-if="deviceInterfaces.length" class="q-mt-sm">
                <div class="text-caption text-grey-6 q-mb-xs">Interfaces ({{ deviceInterfaces.length }}):</div>
                <q-virtual-scroll :items="deviceInterfaces" v-slot="{ item }" style="max-height:300px">
                  <div class="topo-detail-if" :class="{ 'text-red': item.status === 'down' && item.speed > 0 }">
                    <span class="topo-if-status" :class="item.status === 'up' ? 'bg-green' : item.status === 'down' ? 'bg-red' : 'bg-grey'"></span>
                    {{ item.descr || item.ifDescr || '—' }}
                    <span v-if="item.speed" class="text-grey-5 q-ml-xs">{{ formatSpeed(item.speed) }}</span>
                    <span v-if="item.inErrors > 0" class="text-orange q-ml-xs">err:{{ item.inErrors }}</span>
                  </div>
                </q-virtual-scroll>
              </div>
            </q-card-section>
          </q-card>
        </div>
      </div>

      <div v-else class="text-center q-pa-xl text-grey-5">
        Cliquez sur Rafraîchir pour découvrir la topologie réseau
      </div>
    </div><!-- end topology tab -->

    <!-- ═══════════ TAB: AI ANALYSIS ═══════════ -->
    <div v-show="activeTab === 'analysis'">
      <div v-if="analysisLoading" class="text-center q-pa-xl">
        <q-spinner size="32px" color="primary" />
        <div class="text-grey-6 q-mt-sm">Analyse IA en cours... (lecture des logs SNMP + statut OLTs)</div>
      </div>

      <div v-else-if="analysisResult" class="analysis-container">
        <!-- Severity banner -->
        <q-banner :class="'analysis-severity analysis-severity--' + (analysisResult.severity || 'unknown')" rounded>
          <template #avatar>
            <q-icon :name="severityIcon(analysisResult.severity)" size="28px" />
          </template>
          <div class="text-h6">{{ analysisResult.summary_fr || analysisResult.root_cause || 'Analyse terminée' }}</div>
          <div class="text-caption">
            Sévérité: {{ (analysisResult.severity || 'N/A').toUpperCase() }}
            <span v-if="analysisResult.network_health_score != null"> — Score réseau: {{ analysisResult.network_health_score }}/100</span>
            <span v-if="analysisResult.trending"> — Tendance: {{ analysisResult.trending }}</span>
            <span v-if="analysisResult._logSummary" class="q-ml-sm text-grey-4">
              ({{ analysisResult._logSummary.totalTraps }} traps, {{ analysisResult._logSummary.oltCount }} OLTs, {{ analysisResult._logSummary.stuckOffline }} ONUs bloqués)
            </span>
          </div>
        </q-banner>

        <!-- OLT Health Grid -->
        <div class="q-mt-md" v-if="analysisResult.olt_health?.length">
          <div class="text-subtitle2 q-mb-sm">État des OLTs</div>
          <div class="row q-col-gutter-sm">
            <div v-for="olt in analysisResult.olt_health" :key="olt.name" class="col-auto">
              <q-card flat bordered class="olt-health-card" :class="'olt-health--' + olt.status">
                <q-card-section class="q-pa-sm">
                  <div class="row items-center q-gutter-xs">
                    <q-icon :name="olt.status === 'ok' ? 'check_circle' : olt.status === 'warning' ? 'warning' : olt.status === 'critical' ? 'error' : 'cloud_off'"
                      :color="olt.status === 'ok' ? 'green' : olt.status === 'warning' ? 'orange' : 'red'" size="18px" />
                    <div>
                      <div class="text-weight-bold" style="font-size:12px">{{ olt.name }}</div>
                      <div class="text-caption text-grey-6">{{ olt.online }}/{{ olt.total }} ONUs</div>
                    </div>
                  </div>
                  <div v-if="olt.issues_fr && olt.issues_fr !== 'OK'" class="text-caption text-warning q-mt-xs">{{ olt.issues_fr }}</div>
                </q-card-section>
              </q-card>
            </div>
          </div>
        </div>

        <!-- Active Issues -->
        <div class="q-mt-md" v-if="analysisResult.active_issues?.length">
          <div class="text-subtitle2 q-mb-sm">Problèmes actifs</div>
          <q-card v-for="(issue, i) in analysisResult.active_issues" :key="i" flat bordered class="q-mb-sm">
            <q-card-section class="q-pa-sm">
              <div class="row items-center q-gutter-sm q-mb-xs">
                <q-badge :color="issue.urgency === 'immédiat' ? 'red' : issue.urgency === 'court-terme' ? 'orange' : issue.urgency === 'surveillance' ? 'blue' : 'grey'"
                  :label="issue.urgency || 'info'" />
                <q-badge outline color="grey-7" :label="issue.type?.replace(/_/g, ' ')" />
                <span class="text-weight-bold" style="font-size:13px">{{ issue.olt }}</span>
                <span v-if="issue.affected_customers" class="text-caption text-grey-6">~{{ issue.affected_customers }} clients</span>
              </div>
              <div class="analysis-text" style="font-size:13px">{{ issue.description_fr }}</div>
              <div v-if="issue.action_fr" class="text-caption text-primary q-mt-xs">
                <q-icon name="play_arrow" size="14px" /> {{ issue.action_fr }}
              </div>
              <div v-if="issue.evidence?.length" class="q-mt-xs">
                <span v-for="(ev, ei) in issue.evidence" :key="ei" class="analysis-evidence">{{ ev }}</span>
              </div>
            </q-card-section>
          </q-card>
        </div>

        <!-- No issues -->
        <q-banner v-if="!analysisResult.active_issues?.length && analysisResult.severity === 'normal'" class="q-mt-md bg-green-1 text-green-9" rounded>
          <template #avatar><q-icon name="check_circle" color="green" /></template>
          Aucun problème détecté. Le réseau fonctionne normalement.
        </q-banner>

        <!-- Next check recommendation -->
        <div v-if="analysisResult.next_check_fr" class="text-caption text-grey-6 q-mt-md q-pa-sm" style="background:#f8fafc;border-radius:6px">
          <q-icon name="schedule" size="14px" /> {{ analysisResult.next_check_fr }}
        </div>

        <!-- Raw JSON toggle -->
        <q-expansion-item label="Données brutes (JSON)" dense class="q-mt-md text-grey-6">
          <pre class="analysis-raw">{{ JSON.stringify(analysisResult, null, 2) }}</pre>
        </q-expansion-item>
      </div>

      <div v-else class="text-center q-pa-xl text-grey-5">
        Lancez une analyse IA pour obtenir un diagnostic réseau basé sur les logs.<br>
        <span class="text-caption">L'IA analyse les SNMP traps des OLTs, le statut live des ONUs, et les incidents actifs.</span>
      </div>
    </div><!-- end analysis tab -->

    <!-- Add / Edit OLT Modal -->
    <q-dialog v-model="showOltModal" persistent>
      <q-card style="min-width: 400px">
        <q-card-section>
          <div class="text-h6">{{ editingOltIp ? 'Modifier OLT' : 'Ajouter un OLT' }}</div>
        </q-card-section>
        <q-card-section class="q-gutter-sm">
          <q-input v-model="oltForm.ip" dense outlined label="Adresse IP" :disable="!!editingOltIp" />
          <q-input v-model="oltForm.name" dense outlined label="Nom / Localisation" />
          <q-input v-model="oltForm.address" dense outlined label="Adresse civique" />
          <div class="row q-col-gutter-sm">
            <div class="col-6">
              <q-input v-model="oltForm.latitude" dense outlined label="Latitude" type="number" step="any" />
            </div>
            <div class="col-6">
              <q-input v-model="oltForm.longitude" dense outlined label="Longitude" type="number" step="any" />
            </div>
          </div>
          <q-select v-model="oltForm.tech" dense outlined label="Technologie"
            :options="[{ label: 'Huawei', value: 1 }, { label: 'Raisecom', value: 2 }, { label: 'TP-Link', value: 3 }]"
            emit-value map-options @update:model-value="onTechChange" />
          <q-input v-model="oltForm.snmpCommunity" dense outlined label="SNMP Community" />
          <q-select v-model="oltForm.snmpType" dense outlined label="SNMP Type"
            :options="[{ label: 'TP-Link', value: 'tplink' }, { label: 'Raisecom', value: 'raisecom' }]"
            emit-value map-options />
        </q-card-section>
        <q-card-actions align="right">
          <q-btn flat no-caps label="Annuler" color="grey-7" @click="closeOltModal" />
          <q-btn unelevated no-caps :label="editingOltIp ? 'Sauvegarder' : 'Ajouter'" color="primary"
            :loading="saving" :disable="!oltForm.ip || !oltForm.name" @click="saveOlt" />
        </q-card-actions>
      </q-card>
    </q-dialog>
  </q-page>
</template>

<script setup>
import { ref, computed, onMounted, watch, nextTick, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { listDocs, updateDoc, createDoc } from 'src/api/erp'
import { Notify } from 'quasar'

const router = useRouter()
import { HUB_URL as hubUrl } from 'src/config/hub'
const activeTab = ref('gpon')
const loading = ref(false)
const saving = ref(false)
const search = ref('')
const locations = ref([])
const oltRegistry = ref([]) // OLTs with metadata (address, coords)
const snmpStats = reactive({}) // keyed by OLT IP → { onuCount, onlineCount, lastPoll, lastError }

// Poller pause (test phase) ─ hub /admin/pollers state
const pollerLoading = ref(false)
const pollerState = reactive({
  device: { paused: false, lastChange: null, reason: '' },
  olt:    { paused: false, lastChange: null, reason: '' },
})
async function fetchPollerState () {
  pollerLoading.value = true
  try {
    const res = await fetch(`${hubUrl}/admin/pollers`)
    if (res.ok) {
      const data = await res.json()
      if (data.device) Object.assign(pollerState.device, data.device)
      if (data.olt) Object.assign(pollerState.olt, data.olt)
    }
  } catch (e) { /* silent — leave defaults */ }
  finally { pollerLoading.value = false }
}
async function setPollerPaused (kind, paused) {
  pollerLoading.value = true
  try {
    const res = await fetch(`${hubUrl}/admin/pollers`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ [kind]: { paused, reason: paused ? 'Test phase — ops SPA toggle' : '' } }),
    })
    if (res.ok) {
      const data = await res.json()
      if (data.state?.device) Object.assign(pollerState.device, data.state.device)
      if (data.state?.olt) Object.assign(pollerState.olt, data.state.olt)
      Notify.create({
        type: paused ? 'warning' : 'positive',
        message: `Poller ${kind === 'device' ? 'ONT' : 'OLT'} ${paused ? 'mis en pause' : 'réactivé'}`,
        position: 'top', timeout: 1800,
      })
    } else throw new Error(`HTTP ${res.status}`)
  } catch (e) {
    Notify.create({ type: 'negative', message: `Échec: ${e.message}`, position: 'top' })
  } finally { pollerLoading.value = false }
}
function formatPollerTs (iso) {
  if (!iso) return ''
  try { return new Date(iso).toLocaleString('fr-CA', { hour: '2-digit', minute: '2-digit', day: '2-digit', month: 'short' }) }
  catch { return iso }
}

// Edit location state
const editingLoc = ref(null)
const editForm = reactive({ olt_ip: '', slot: 0, port: 0, ont_id: 0, ont_serial: '', address_line: '', city: '', postal_code: '' })

// Add location state
const addingAt = ref(null) // "ip/slot/port"
const addForm = reactive({ customer: '', customerSearch: '', customerName: '', address_line: '', city: '', postal_code: '', ont_id: 0, ont_serial: '' })
const customerResults = ref([])
let customerSearchTimer = null

// OLT modal state
const showOltModal = ref(false)
const editingOltIp = ref(null)
const oltForm = reactive({ ip: '', name: '', address: '', latitude: null, longitude: null, tech: 2, snmpCommunity: 'targosnmp', snmpType: 'raisecom' })

// Tree expand state
const expandedOlts = reactive({})
const expandedSlots = reactive({})
const expandedPorts = reactive({})

let searchTimer = null

// Build OLT tree from flat locations + registry
const olts = computed(() => {
  const map = {}

  // Seed from registry (so empty OLTs show up)
  for (const reg of oltRegistry.value) {
    map[reg.ip] = { ip: reg.ip, name: reg.name || '', address: reg.address || '', latitude: reg.latitude, longitude: reg.longitude, tech: reg.tech, slots: {} }
  }

  for (const loc of locations.value) {
    if (!loc.olt_ip) continue
    const parsed = parseOltPort(loc.olt_port)
    const ip = loc.olt_ip
    if (!map[ip]) map[ip] = { ip, name: loc.olt_name || '', address: '', slots: {} }
    if (!map[ip].name && loc.olt_name) map[ip].name = loc.olt_name
    const slotNum = parsed.slot
    if (!map[ip].slots[slotNum]) map[ip].slots[slotNum] = { num: slotNum, ports: {} }
    const portNum = parsed.port
    if (!map[ip].slots[slotNum].ports[portNum]) map[ip].slots[slotNum].ports[portNum] = { num: portNum, locations: [] }
    map[ip].slots[slotNum].ports[portNum].locations.push({
      ...loc,
      ont_id: loc.ont_id ?? parsed.ontId,
    })
  }

  return Object.values(map)
    .map(olt => {
      const slotArr = Object.values(olt.slots)
        .map(s => {
          const portArr = Object.values(s.ports)
            .map(p => { p.locations.sort((a, b) => (a.ont_id || 0) - (b.ont_id || 0)); return p })
            .sort((a, b) => a.num - b.num)
          return { num: s.num, ports: portArr, portCount: portArr.length, locationCount: portArr.reduce((t, p) => t + p.locations.length, 0) }
        })
        .sort((a, b) => a.num - b.num)
      return {
        ip: olt.ip, name: olt.name, address: olt.address || '',
        latitude: olt.latitude, longitude: olt.longitude, tech: olt.tech,
        slots: slotArr,
        slotCount: slotArr.length,
        locationCount: slotArr.reduce((t, s) => t + s.locationCount, 0),
      }
    })
    .sort((a, b) => a.ip.localeCompare(b.ip, undefined, { numeric: true }))
})

const totalLocations = computed(() => locations.value.filter(l => l.olt_ip).length)
const oltIpOptions = computed(() => olts.value.map(o => ({ label: `${o.name} (${o.ip})`, value: o.ip })))

// Filter tree by search
const filteredOlts = computed(() => {
  const q = search.value.trim().toLowerCase()
  if (!q) return olts.value

  return olts.value
    .map(olt => {
      if (olt.ip.toLowerCase().includes(q) || olt.name.toLowerCase().includes(q) || (olt.address || '').toLowerCase().includes(q)) return olt
      const filteredSlots = olt.slots
        .map(slot => {
          const filteredPorts = slot.ports
            .map(port => {
              const filteredLocs = port.locations.filter(loc =>
                (loc.location_name || '').toLowerCase().includes(q) ||
                (loc.customer || '').toLowerCase().includes(q) ||
                (loc.address_line || '').toLowerCase().includes(q) ||
                (loc.city || '').toLowerCase().includes(q) ||
                (loc.ont_serial || '').toLowerCase().includes(q) ||
                String(loc.ont_id).includes(q) ||
                String(port.num).includes(q) ||
                String(slot.num).includes(q)
              )
              if (!filteredLocs.length) return null
              return { ...port, locations: filteredLocs }
            })
            .filter(Boolean)
          if (!filteredPorts.length) return null
          return { ...slot, ports: filteredPorts, portCount: filteredPorts.length, locationCount: filteredPorts.reduce((t, p) => t + p.locations.length, 0) }
        })
        .filter(Boolean)
      if (!filteredSlots.length) return null
      return { ...olt, slots: filteredSlots, slotCount: filteredSlots.length, locationCount: filteredSlots.reduce((t, s) => t + s.locationCount, 0) }
    })
    .filter(Boolean)
})

function parseOltPort (str) {
  if (!str) return { frame: 0, slot: 0, port: 0, ontId: 0 }
  const m = str.match(/(\d+)\/(\d+)\/(\d+)/)
  const ontM = str.match(/ONT:(\d+)/)
  return { frame: m ? parseInt(m[1]) : 0, slot: m ? parseInt(m[2]) : 0, port: m ? parseInt(m[3]) : 0, ontId: ontM ? parseInt(ontM[1]) : 0 }
}

function toggleOlt (ip) { expandedOlts[ip] = !expandedOlts[ip] }
function toggleSlot (ip, slot) { expandedSlots[ip + '/' + slot] = !expandedSlots[ip + '/' + slot] }
function togglePort (ip, slot, port) { expandedPorts[ip + '/' + slot + '/' + port] = !expandedPorts[ip + '/' + slot + '/' + port] }

function goToClient (customerId) {
  if (customerId) router.push('/clients/' + customerId)
}

// ── OLT CRUD ──

function startEditOlt (olt) {
  editingOltIp.value = olt.ip
  oltForm.ip = olt.ip
  oltForm.name = olt.name || ''
  oltForm.address = olt.address || ''
  oltForm.latitude = olt.latitude || null
  oltForm.longitude = olt.longitude || null
  oltForm.tech = olt.tech || 2
  oltForm.snmpCommunity = olt.snmpCommunity || 'targosnmp'
  oltForm.snmpType = olt.snmpType || (olt.tech === 2 ? 'raisecom' : 'tplink')
  showOltModal.value = true
}

function onTechChange (val) {
  oltForm.snmpType = val === 2 ? 'raisecom' : 'tplink'
}

function closeOltModal () {
  showOltModal.value = false
  editingOltIp.value = null
  oltForm.ip = ''; oltForm.name = ''; oltForm.address = ''; oltForm.latitude = null; oltForm.longitude = null; oltForm.tech = 2
  oltForm.snmpCommunity = 'targosnmp'; oltForm.snmpType = 'raisecom'
}

async function saveOlt () {
  saving.value = true
  try {
    const data = {
      olt_ip: oltForm.ip,
      olt_name: oltForm.name,
      olt_address: oltForm.address || '',
      olt_latitude: oltForm.latitude || 0,
      olt_longitude: oltForm.longitude || 0,
      olt_tech: oltForm.tech || 2,
    }

    if (editingOltIp.value) {
      // Update all Service Locations with this OLT IP
      const locsToUpdate = locations.value.filter(l => l.olt_ip === editingOltIp.value)
      for (const loc of locsToUpdate) {
        await updateDoc('Service Location', loc.name, { olt_name: oltForm.name, network_notes: `OLT: ${oltForm.name}` })
        loc.olt_name = oltForm.name
      }
      // Update registry
      const reg = oltRegistry.value.find(r => r.ip === editingOltIp.value)
      const snmpFields = { snmpCommunity: oltForm.snmpCommunity, snmpType: oltForm.snmpType }
      if (reg) Object.assign(reg, { name: oltForm.name, address: oltForm.address, latitude: oltForm.latitude, longitude: oltForm.longitude, tech: oltForm.tech, ...snmpFields })
      else oltRegistry.value.push({ ip: oltForm.ip, name: oltForm.name, address: oltForm.address, latitude: oltForm.latitude, longitude: oltForm.longitude, tech: oltForm.tech, ...snmpFields })
      saveOltRegistry()
      syncOltToHub(oltRegistry.value.find(r => r.ip === editingOltIp.value) || { ip: oltForm.ip, snmpCommunity: oltForm.snmpCommunity, snmpType: oltForm.snmpType, name: oltForm.name })
      Notify.create({ type: 'positive', message: `OLT ${oltForm.name} mis à jour (${locsToUpdate.length} locations)` })
    } else {
      // New OLT — add to registry
      if (olts.value.find(o => o.ip === oltForm.ip)) {
        Notify.create({ type: 'warning', message: 'Un OLT avec cette IP existe déjà' })
        saving.value = false
        return
      }
      oltRegistry.value.push({ ip: oltForm.ip, name: oltForm.name, address: oltForm.address, latitude: oltForm.latitude, longitude: oltForm.longitude, tech: oltForm.tech, snmpCommunity: oltForm.snmpCommunity, snmpType: oltForm.snmpType })
      saveOltRegistry()
      Notify.create({ type: 'positive', message: `OLT ${oltForm.name} ajouté` })
    }
    closeOltModal()
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur: ' + e.message })
  } finally {
    saving.value = false
  }
}

// Persist OLT registry to localStorage (lightweight — will be backed by doctype later)
function saveOltRegistry () {
  localStorage.setItem('olt-registry', JSON.stringify(oltRegistry.value))
}

function loadOltRegistry () {
  try {
    const stored = localStorage.getItem('olt-registry')
    if (stored) oltRegistry.value = JSON.parse(stored)
  } catch { /* ignore */ }
}

// ── SNMP Stats ──

async function fetchSnmpStats () {
  try {
    const res = await fetch(`${hubUrl}/olt/stats`)
    if (!res.ok) return
    const data = await res.json()
    for (const s of data) {
      snmpStats[s.host] = { onuCount: s.onuCount || 0, onlineCount: s.onlineCount || 0, lastPoll: s.lastPoll, lastDuration: s.lastDuration, lastError: s.lastError || null }
    }
  } catch { /* hub unreachable — leave stats empty */ }
}

async function syncOltToHub (oltReg) {
  try {
    await fetch(`${hubUrl}/olt/config`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ host: oltReg.ip, name: oltReg.name, community: oltReg.snmpCommunity || 'targosnmp', type: oltReg.snmpType || 'tplink' }),
    })
  } catch { /* silent */ }
}

// ── Edit Location ──

function startEdit (loc) {
  const parsed = parseOltPort(loc.olt_port)
  editingLoc.value = loc.name
  addingAt.value = null
  editForm.olt_ip = loc.olt_ip || ''
  editForm.slot = parsed.slot
  editForm.port = parsed.port
  editForm.ont_id = loc.ont_id ?? parsed.ontId
  editForm.ont_serial = loc.ont_serial || ''
  editForm.address_line = loc.address_line || ''
  editForm.city = loc.city || ''
  editForm.postal_code = loc.postal_code || ''
}

function cancelEdit () { editingLoc.value = null }

async function saveEdit () {
  saving.value = true
  try {
    const frame = 0
    const oltPort = `${editForm.olt_ip} ${frame}/${editForm.slot}/${editForm.port}` + (editForm.ont_id ? ` ONT:${editForm.ont_id}` : '')
    const olt = olts.value.find(o => o.ip === editForm.olt_ip)
    const oltName = olt ? olt.name : ''

    await updateDoc('Service Location', editingLoc.value, {
      olt_port: oltPort,
      olt_ip: editForm.olt_ip,
      olt_name: oltName,
      ont_id: editForm.ont_id || 0,
      ont_serial: editForm.ont_serial || '',
      network_notes: oltName ? `OLT: ${oltName}` : '',
      address_line: editForm.address_line,
      city: editForm.city,
      postal_code: editForm.postal_code,
    })

    const loc = locations.value.find(l => l.name === editingLoc.value)
    if (loc) {
      loc.olt_port = oltPort; loc.olt_ip = editForm.olt_ip; loc.olt_name = oltName
      loc.ont_id = editForm.ont_id; loc.ont_serial = editForm.ont_serial
      loc.address_line = editForm.address_line; loc.city = editForm.city; loc.postal_code = editForm.postal_code
    }

    Notify.create({ type: 'positive', message: 'Location mise à jour' })
    editingLoc.value = null
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur: ' + e.message })
  } finally {
    saving.value = false
  }
}

// ── Add Location ──

function startAddLocation (olt, slotNum, portNum) {
  editingLoc.value = null
  addingAt.value = `${olt.ip}/${slotNum}/${portNum}`
  addForm.customer = ''; addForm.customerSearch = ''; addForm.customerName = ''
  addForm.address_line = ''; addForm.city = ''; addForm.postal_code = ''
  addForm.ont_id = 0; addForm.ont_serial = ''
  customerResults.value = []
  // Auto-expand the port
  expandedOlts[olt.ip] = true
  expandedSlots[olt.ip + '/' + slotNum] = true
  expandedPorts[olt.ip + '/' + slotNum + '/' + portNum] = true
}

function searchCustomers () {
  clearTimeout(customerSearchTimer)
  customerSearchTimer = setTimeout(async () => {
    const q = addForm.customerSearch.trim()
    if (q.length < 2) { customerResults.value = []; return }
    try {
      customerResults.value = await listDocs('Customer', {
        or_filters: [['customer_name', 'like', `%${q}%`], ['name', 'like', `%${q}%`]],
        fields: ['name', 'customer_name'],
        limit: 8,
        orderBy: 'customer_name asc',
      })
    } catch { customerResults.value = [] }
  }, 300)
}

function selectCustomer (c) {
  addForm.customer = c.name
  addForm.customerName = c.customer_name
  addForm.customerSearch = c.customer_name
  customerResults.value = []
}

async function saveNewLocation () {
  if (!addForm.customer) return
  saving.value = true
  try {
    const [oltIp, slotStr, portStr] = addingAt.value.split('/')
    const slot = parseInt(slotStr); const port = parseInt(portStr)
    const frame = 0
    const oltPort = `${oltIp} ${frame}/${slot}/${port}` + (addForm.ont_id ? ` ONT:${addForm.ont_id}` : '')
    const olt = olts.value.find(o => o.ip === oltIp)
    const oltName = olt ? olt.name : ''

    const newLoc = await createDoc('Service Location', {
      customer: addForm.customer,
      location_name: addForm.customerName,
      address_line: addForm.address_line,
      city: addForm.city,
      postal_code: addForm.postal_code,
      connection_type: 'Fibre FTTH',
      olt_port: oltPort,
      olt_ip: oltIp,
      olt_name: oltName,
      ont_id: addForm.ont_id || 0,
      ont_serial: addForm.ont_serial || '',
      network_notes: oltName ? `OLT: ${oltName}` : '',
    })

    locations.value.push({
      name: newLoc.name,
      customer: addForm.customer,
      location_name: addForm.customerName,
      address_line: addForm.address_line,
      city: addForm.city,
      postal_code: addForm.postal_code,
      olt_port: oltPort, olt_ip: oltIp, olt_name: oltName,
      ont_id: addForm.ont_id, ont_serial: addForm.ont_serial,
    })

    Notify.create({ type: 'positive', message: `${addForm.customerName} ajouté sur port ${slot}/${port}` })
    addingAt.value = null
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur: ' + e.message })
  } finally {
    saving.value = false
  }
}

// ── Search ──

function clearSearch () { search.value = '' }

function onSearchInput () {
  clearTimeout(searchTimer)
  searchTimer = setTimeout(() => {
    if (search.value.trim()) {
      for (const olt of filteredOlts.value) {
        expandedOlts[olt.ip] = true
        for (const slot of olt.slots) {
          expandedSlots[olt.ip + '/' + slot.num] = true
          for (const port of slot.ports) {
            expandedPorts[olt.ip + '/' + slot.num + '/' + port.num] = true
          }
        }
      }
    }
  }, 300)
}

// ── Load data ──

async function loadLocations () {
  loading.value = true
  try {
    const data = await listDocs('Service Location', {
      filters: { connection_type: 'Fibre FTTH' },
      fields: ['name', 'customer', 'location_name', 'address_line', 'city', 'postal_code', 'olt_port', 'olt_ip', 'olt_name', 'ont_id', 'ont_serial'],
      limit: 0,
      orderBy: 'olt_ip asc, olt_port asc',
    })
    locations.value = data
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur chargement: ' + e.message })
  } finally {
    loading.value = false
  }
}

// ── Network Map tab (Cytoscape canvas) ──

const mapLoading = ref(false)
const mapData = ref(null)
const mapSelectedSite = ref(null)
const mapSelectedSiteId = ref(null)
const cyContainer = ref(null)
let cyInstance = null
let cytoscape = null // chargé à la demande (lib ~350 KB) — seulement quand on ouvre l'onglet Carte

const mapSelectedSiteLinks = computed(() => {
  if (!mapData.value || !mapSelectedSiteId.value) return []
  return mapData.value.links.filter(l => l.from === mapSelectedSiteId.value || l.to === mapSelectedSiteId.value)
})

async function loadNetworkMap () {
  mapLoading.value = true
  try {
    const resp = await fetch(`${hubUrl}/network/map`)
    mapData.value = await resp.json()
    if (!cytoscape) cytoscape = (await import('cytoscape')).default // import dynamique : sort la lib du chunk NetworkPage
    await nextTick()
    renderCytoscape()
  } catch (e) {
    Notify.create({ type: 'negative', message: `Erreur carte: ${e.message}` })
  } finally {
    mapLoading.value = false
  }
}

function renderCytoscape () {
  if (!cyContainer.value || !mapData.value) return
  if (cyInstance) { cyInstance.destroy(); cyInstance = null }

  const elements = []
  const data = mapData.value

  // Add site nodes
  for (const [id, site] of Object.entries(data.sites)) {
    const isDatacenter = site.type === 'datacenter'
    const olts = site.devices.filter(d => d.type === 'olt')
    const routers = site.devices.filter(d => d.type === 'router')
    const onuLabel = site.onuTotal > 0 ? `\n${site.onuOnline}/${site.onuTotal} ONUs` : ''
    const oltLabel = olts.length > 0 ? `\n${olts.length} OLT${olts.length > 1 ? 's' : ''}` : ''

    elements.push({
      group: 'nodes',
      data: {
        id,
        label: `${site.name}${oltLabel}${onuLabel}`,
        status: site.status,
        type: site.type,
        onuTotal: site.onuTotal,
        onuOnline: site.onuOnline,
        oltCount: olts.length,
        routerCount: routers.length,
        isDatacenter,
      },
      // Use lat/lng for initial positioning (scaled to canvas)
      position: site.lat && site.lng ? {
        x: (site.lng + 74.5) * 2000,
        y: (45.6 - site.lat) * 3000,
      } : undefined,
    })
  }

  // Add link edges
  for (const link of data.links) {
    const speedLabel = link.speed_gbps ? `${link.speed_gbps}G` : ''
    const distLabel = link.distance_km ? `${link.distance_km}km` : ''
    elements.push({
      group: 'edges',
      data: {
        id: link.id || `${link.from}-${link.to}`,
        source: link.from,
        target: link.to,
        label: [speedLabel, distLabel].filter(Boolean).join(' / '),
        speed: link.speed_gbps || 0,
        status: link.status,
        linkType: link.type,
      },
    })
  }

  cyInstance = cytoscape({
    container: cyContainer.value,
    elements,
    userZoomingEnabled: true,
    userPanningEnabled: true,
    boxSelectionEnabled: false,
    style: [
      {
        selector: 'node',
        style: {
          'label': 'data(label)',
          'text-valign': 'bottom',
          'text-halign': 'center',
          'font-size': '10px',
          'font-family': 'Inter, system-ui, sans-serif',
          'text-wrap': 'wrap',
          'text-max-width': '100px',
          'color': '#334155',
          'width': 40,
          'height': 40,
          'shape': 'roundrectangle',
          'background-color': '#e2e8f0',
          'border-width': 2,
          'border-color': '#94a3b8',
          'text-margin-y': 6,
        },
      },
      // Status colors
      {
        selector: 'node[status="up"]',
        style: {
          'background-color': '#dcfce7',
          'border-color': '#22c55e',
        },
      },
      {
        selector: 'node[status="degraded"]',
        style: {
          'background-color': '#fef2f2',
          'border-color': '#ef4444',
        },
      },
      {
        selector: 'node[status="unknown"]',
        style: {
          'background-color': '#f1f5f9',
          'border-color': '#cbd5e1',
        },
      },
      // Datacenter nodes bigger
      {
        selector: 'node[?isDatacenter]',
        style: {
          'width': 55,
          'height': 55,
          'font-size': '11px',
          'font-weight': 'bold',
          'shape': 'diamond',
          'border-width': 3,
          'background-color': '#e0e7ff',
          'border-color': '#6366f1',
        },
      },
      // OLT-heavy sites
      {
        selector: 'node[oltCount > 2]',
        style: {
          'width': 48,
          'height': 48,
        },
      },
      // Selected node
      {
        selector: 'node:selected',
        style: {
          'border-color': '#4f46e5',
          'border-width': 3,
          'overlay-color': '#4f46e5',
          'overlay-padding': 4,
          'overlay-opacity': 0.15,
        },
      },
      // Edges
      {
        selector: 'edge',
        style: {
          'width': 2,
          'line-color': '#94a3b8',
          'curve-style': 'bezier',
          'label': 'data(label)',
          'font-size': '8px',
          'font-family': 'monospace',
          'color': '#64748b',
          'text-rotation': 'autorotate',
          'text-background-color': '#ffffff',
          'text-background-opacity': 0.85,
          'text-background-padding': '2px',
        },
      },
      // Speed-based edge width
      { selector: 'edge[speed >= 100]', style: { 'width': 5, 'line-color': '#6366f1' } },
      { selector: 'edge[speed >= 25]', style: { 'width': 4, 'line-color': '#818cf8' } },
      { selector: 'edge[speed >= 10]', style: { 'width': 3, 'line-color': '#a5b4fc' } },
      // DWDM links dashed
      { selector: 'edge[linkType="dwdm"]', style: { 'line-style': 'dashed', 'line-dash-pattern': [8, 4] } },
      // Link status
      { selector: 'edge[status="up"]', style: { 'line-color': '#22c55e' } },
      { selector: 'edge[status="degraded"]', style: { 'line-color': '#ef4444', 'line-style': 'dashed' } },
    ],
    layout: {
      name: 'preset', // Use geo coordinates
      fit: true,
      padding: 40,
    },
  })

  // Click handler
  cyInstance.on('tap', 'node', (evt) => {
    const nodeId = evt.target.id()
    mapSelectedSiteId.value = nodeId
    mapSelectedSite.value = data.sites[nodeId]
  })

  // Click on background clears selection
  cyInstance.on('tap', (evt) => {
    if (evt.target === cyInstance) {
      mapSelectedSite.value = null
      mapSelectedSiteId.value = null
    }
  })
}

// Auto-load map when tab switches
watch(activeTab, (tab) => {
  if (tab === 'map' && !mapData.value) loadNetworkMap()
})

// ── Topology tab ──

const topoLoading = ref(false)
const topoData = ref(null)
const selectedDevice = ref(null)
const deviceInterfaces = ref([])

const topoByLevel = computed(() => {
  if (!topoData.value) return {}
  const levels = {}
  for (const dev of topoData.value.topology.devices) {
    const lvl = dev.level ?? 4
    if (!levels[lvl]) levels[lvl] = []
    levels[lvl].push(dev)
  }
  // Sort each level by host
  for (const lvl in levels) levels[lvl].sort((a, b) => a.host.localeCompare(b.host, undefined, { numeric: true }))
  return levels
})

const oltChains = computed(() => {
  if (!topoData.value) return []
  return topoData.value.chains.filter(c => c.leafType === 'olt')
})

const selectedDeviceData = computed(() => {
  if (!selectedDevice.value || !topoData.value) return null
  return topoData.value.topology.devices.find(d => d.host === selectedDevice.value)
})

const deviceLinks = computed(() => {
  if (!selectedDevice.value || !topoData.value) return []
  return topoData.value.topology.links.filter(l => l.from === selectedDevice.value || l.to === selectedDevice.value)
})

function topoTypeLabel (type) {
  const labels = { oltGateways: 'OLT Gateways', edgeRouters: 'Edge Routers', coreSwitches: 'Core Switches', servers: 'Serveurs', publicInfra: 'Infra Publique', unknown: 'Inconnu' }
  return labels[type] || type
}

function healthClass (health) {
  if (health == null) return 'topo-h-unknown'
  if (health >= 90) return 'topo-h-good'
  if (health >= 70) return 'topo-h-warn'
  return 'topo-h-crit'
}

function devClass (dev) {
  const hasKuma = dev.kuma != null
  const kumaUp = dev.kuma?.status === 1
  const kumaDown = dev.kuma?.status === 0
  const noData = !hasKuma && (dev.ifSummary?.activeTotal || 0) === 0 // no Kuma + no SNMP = unknown
  return {
    'topo-device--selected': selectedDevice.value === dev.host,
    'topo-device--danger': kumaDown, // only red if Kuma confirms DOWN
    'topo-device--ok': kumaUp, // green if Kuma confirms UP
    'topo-device--nodata': noData, // grey if no monitoring data at all
    'topo-device--warning': !hasKuma && !noData && dev.ifSummary?.health < 50, // some SNMP but unhealthy
  }
}

function kumaClass (dev) {
  if (!dev.kuma) return 'topo-kuma--unknown'
  if (dev.kuma.status === 1) return 'topo-kuma--up'
  if (dev.kuma.status === 0) return 'topo-kuma--down'
  return 'topo-kuma--pending'
}

function kumaTitle (dev) {
  if (!dev.kuma) return 'Pas monitoré dans Kuma'
  const st = dev.kuma.status === 1 ? 'UP' : dev.kuma.status === 0 ? 'DOWN' : 'Pending'
  const rt = dev.kuma.responseTime ? ` (${Math.round(dev.kuma.responseTime)}ms)` : ''
  return `${dev.kuma.name}: ${st}${rt}`
}

function formatSpeed (speed) {
  if (!speed) return ''
  if (speed >= 1e9) return (speed / 1e9).toFixed(0) + 'G'
  if (speed >= 1e6) return (speed / 1e6).toFixed(0) + 'M'
  return speed + ''
}

function selectDevice (dev) {
  selectedDevice.value = selectedDevice.value === dev.host ? null : dev.host
  if (selectedDevice.value) loadDeviceInterfaces(dev.host)
}

async function loadDeviceInterfaces (host) {
  try {
    const res = await fetch(`${hubUrl}/network/health/${encodeURIComponent(host)}`)
    if (!res.ok) return
    const data = await res.json()
    deviceInterfaces.value = data.interfaces || []
  } catch { deviceInterfaces.value = [] }
}

async function loadTopology () {
  topoLoading.value = true
  try {
    const res = await fetch(`${hubUrl}/network/dependency-map`)
    if (!res.ok) throw new Error('HTTP ' + res.status)
    topoData.value = await res.json()
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur topologie: ' + e.message })
  } finally {
    topoLoading.value = false
  }
}

// ── AI Analysis tab ──

const analysisLoading = ref(false)
const analysisResult = ref(null)

function severityIcon (s) {
  if (s === 'critical') return 'error'
  if (s === 'high') return 'warning'
  if (s === 'medium') return 'info'
  return 'check_circle'
}

async function runAnalysis () {
  analysisLoading.value = true
  analysisResult.value = null
  try {
    const res = await fetch(`${hubUrl}/network/analyze`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ context: 'Analyse complète du réseau — identifie les pannes, risques et recommandations' }),
    })
    if (!res.ok) throw new Error('HTTP ' + res.status)
    analysisResult.value = await res.json()
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur analyse: ' + e.message })
  } finally {
    analysisLoading.value = false
  }
}

onMounted(() => {
  loadOltRegistry()
  loadLocations()
  fetchSnmpStats()
  fetchPollerState()
  // Auto-load topology if on that tab
  if (activeTab.value === 'topology') loadTopology()
})
</script>

<style lang="scss">
.net-tree { max-width: 1100px; }

.poller-card {
  padding: 14px 16px;
  background: var(--ops-surface, #fff);
  border: 1px solid var(--ops-border, #e2e8f0);
  border-radius: 8px;
  box-shadow: 0 1px 2px rgba(15, 23, 42, .04);
}
.poller-chip {
  margin-left: 4px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .02em;
  .q-icon { font-size: 14px; }
}

.net-olt { margin-bottom: 4px; }
.net-olt-header {
  display: flex; align-items: center; gap: 6px;
  padding: 10px 12px; cursor: pointer;
  background: var(--ops-surface); border: 1px solid var(--ops-border);
  border-radius: 6px;
  font-weight: 600; font-size: 14px;
  &:hover { background: var(--ops-bg-hover); }
  &:hover .net-olt-edit-btn { opacity: 1; }
}
.net-olt-icon { font-size: 16px; }
.net-olt-name { color: var(--ops-text); }
.net-olt-ip { color: var(--ops-text-muted); font-family: monospace; font-size: 12px; font-weight: 400; }
.net-olt-addr { color: #94a3b8; font-size: 12px; font-weight: 400; font-style: italic; }
.net-olt-count { margin-left: auto; color: var(--ops-text-muted); font-size: 12px; font-weight: 400; }
.net-olt-slots { color: var(--ops-text-muted); font-size: 12px; font-weight: 400; }
.net-olt-edit-btn { opacity: 0; transition: opacity .15s; }

/* Mobile — arbre GPON : les en-têtes (OLT/slot/port) empilent trop d'infos sur une ligne (nom·IP·adresse·nb clients·SNMP·slots·éditer) → on enroule.
   Les boutons d'action révélés au survol (opacity:0) sont inaccessibles au doigt → toujours visibles sur tactile. */
@media (max-width: 599px) {
  .net-olt-header, .net-slot-header, .net-port-header { flex-wrap: wrap; }
  .net-olt-count, .net-slot-count, .net-port-count { margin-left: 0; }
  .net-olt-edit-btn, .net-port-add-btn, .net-loc-edit-btn { opacity: 1; }
}

.net-snmp-badge {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11px; font-weight: 400; padding: 1px 8px;
  border-radius: 10px; white-space: nowrap;
}
.net-snmp-ok { color: #15803d; background: #dcfce7; }
.net-snmp-err { color: #b91c1c; background: #fee2e2; }
.net-snmp-none { color: #6b7280; background: #f3f4f6; }
.net-snmp-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
}
.net-snmp-dot--green { background: #22c55e; }
.net-snmp-dot--red { background: #ef4444; }
.net-snmp-dot--grey { background: #9ca3af; }

.net-slots { padding-left: 20px; }
.net-slot { margin-top: 2px; }
.net-slot-header {
  display: flex; align-items: center; gap: 4px;
  padding: 7px 10px; cursor: pointer;
  background: #f1f5f9; border-radius: 4px;
  font-size: 13px; font-weight: 500; color: var(--ops-text);
  &:hover { background: var(--ops-bg-hover); }
}
.net-slot-label { font-family: monospace; }
.net-slot-count { margin-left: auto; font-size: 11px; color: var(--ops-text-muted); font-weight: 400; }

.net-ports { padding-left: 20px; }
.net-port { margin-top: 2px; }
.net-port-header {
  display: flex; align-items: center; gap: 4px;
  padding: 5px 8px; cursor: pointer;
  background: #f8fafc; border-radius: 3px;
  font-size: 12px; color: var(--ops-text-muted);
  &:hover { background: var(--ops-bg-hover); }
  &:hover .net-port-add-btn { opacity: 1; }
}
.net-port-label { font-family: monospace; font-weight: 500; color: var(--ops-text); }
.net-port-count { margin-left: auto; font-size: 11px; color: #94a3b8; }
.net-port-add-btn { opacity: 0; transition: opacity .15s; margin-left: 4px; }

.net-locations { padding-left: 24px; }
.net-location {
  border-left: 2px solid var(--ops-border);
  padding: 4px 0 4px 10px; margin: 1px 0;
  &:hover { border-left-color: var(--ops-accent); }
  &:hover .net-loc-edit-btn { opacity: 1; }
}
.net-location-editing { border-left-color: var(--ops-accent); background: #eef2ff; border-radius: 4px; padding: 8px 10px; }

.net-loc-main {
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; flex-wrap: wrap;
}
.net-loc-ont {
  font-family: monospace; font-size: 11px; font-weight: 600;
  color: var(--ops-accent); background: #eef2ff; padding: 1px 6px; border-radius: 3px;
  min-width: 52px; text-align: center;
}
.net-loc-customer {
  font-weight: 500; color: var(--ops-text); cursor: pointer;
  &:hover { color: var(--ops-accent); text-decoration: underline; }
}
.net-loc-address { color: var(--ops-text-muted); }
.net-loc-city { color: #94a3b8; }
.net-loc-serial { font-family: monospace; font-size: 11px; color: #94a3b8; }
.net-loc-edit-btn { opacity: 0; transition: opacity .15s; }

.net-loc-edit {
  display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px;
  padding: 12px; background: var(--ops-surface); border: 1px solid var(--ops-border);
  border-radius: 6px;
}
.net-edit-grid {
  display: flex; gap: 24px; width: 100%; flex-wrap: wrap;
}
.net-edit-section {
  display: flex; flex-direction: column; gap: 6px; min-width: 240px;
}
.net-edit-section-title {
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--ops-text-muted); margin-bottom: 2px;
}
.net-edit-row {
  display: flex; align-items: center; gap: 6px;
  label { font-size: 11px; color: var(--ops-text-muted); min-width: 70px; }
}
.net-edit-field { width: 180px; }
.net-edit-field-lg { width: 260px; }
.net-edit-field-sm { width: 80px; }
.net-edit-actions {
  display: flex; gap: 6px; margin-left: auto; align-items: center; width: 100%; justify-content: flex-end;
  margin-top: 4px; padding-top: 8px; border-top: 1px solid var(--ops-border);
}

.net-customer-results {
  margin-left: 76px; background: var(--ops-surface); border: 1px solid var(--ops-border);
  border-radius: 4px; max-height: 160px; overflow-y: auto; margin-bottom: 4px;
}
.net-customer-result {
  padding: 6px 10px; font-size: 12px; cursor: pointer;
  &:hover, &.active { background: var(--ops-bg-hover); }
}

/* ── Topology tab ── */
.topo-container { max-width: 1400px; }

.topo-summary-card {
  min-width: 100px; border-radius: 8px;
  .topo-summary-count { font-size: 24px; font-weight: 700; color: var(--ops-text); line-height: 1.2; }
  .topo-summary-label { font-size: 11px; color: var(--ops-text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
}

.topo-hierarchy { display: flex; flex-direction: column; gap: 16px; }

.topo-level {
  display: flex; align-items: flex-start; gap: 12px;
}
.topo-level-label {
  min-width: 110px; font-size: 10px; font-weight: 700; letter-spacing: 1px;
  color: var(--ops-text-muted); text-transform: uppercase; padding-top: 10px;
  border-right: 2px solid var(--ops-border); padding-right: 10px; text-align: right;
}
.topo-level-devices {
  display: flex; flex-wrap: wrap; gap: 8px; flex: 1;
}

.topo-device {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 8px 12px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--ops-border); background: var(--ops-surface);
  min-width: 110px; transition: all .15s; position: relative;
  &:hover { border-color: #818cf8; box-shadow: 0 2px 8px rgba(99,102,241,.15); }
}
.topo-device--core { border-left: 3px solid #6366f1; }
.topo-device--edge { border-left: 3px solid #10b981; }
.topo-device--olt { border-left: 3px solid #f59e0b; }
.topo-device--olt-leaf { border-left: 3px solid #ef4444; min-width: 100px; padding: 6px 10px; }
.topo-device--selected { border-color: #6366f1 !important; background: #eef2ff; box-shadow: 0 2px 12px rgba(99,102,241,.2); }
.topo-device--ok { border-color: #22c55e; background: #f0fdf4; }
.topo-device--warning { border-color: #f59e0b; background: #fffbeb; }
.topo-device--danger { border-color: #ef4444; background: #fef2f2; }
.topo-device--nodata { border-color: #d1d5db; background: #f9fafb; opacity: .7; }

.topo-device-host { font-family: monospace; font-size: 11px; font-weight: 600; color: var(--ops-text); }
.topo-device-type { font-size: 10px; color: var(--ops-text-muted); }
.topo-device-stats { font-size: 10px; font-family: monospace; color: var(--ops-text-muted); }
.topo-kuma-label { font-size: 9px; color: var(--ops-text-muted); max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Kuma reachability dot (top-right corner of card) */
.topo-kuma-dot {
  position: absolute; top: 4px; right: 4px;
  width: 8px; height: 8px; border-radius: 50%;
}
.topo-kuma--up { background: #22c55e; box-shadow: 0 0 4px #22c55e88; }
.topo-kuma--down { background: #ef4444; box-shadow: 0 0 4px #ef444488; animation: kuma-pulse 1.5s infinite; }
.topo-kuma--pending { background: #f59e0b; }
.topo-kuma--unknown { background: #9ca3af; }
@keyframes kuma-pulse { 0%,100% { opacity: 1; } 50% { opacity: .4; } }
.topo-device-onus { font-size: 10px; color: var(--ops-text-muted); display: flex; align-items: center; gap: 3px; }

.topo-device-health { display: flex; align-items: center; gap: 4px; width: 100%; }
.topo-health-bar { flex: 1; height: 4px; background: #e5e7eb; border-radius: 2px; overflow: hidden; }
.topo-health-fill { height: 100%; border-radius: 2px; transition: width .3s; }
.topo-health-text { font-size: 9px; font-weight: 600; min-width: 28px; text-align: right; }

.topo-h-good { background: #22c55e; color: #22c55e; }
.topo-h-warn { background: #f59e0b; color: #f59e0b; }
.topo-h-crit { background: #ef4444; color: #ef4444; }
.topo-h-unknown { background: #9ca3af; color: #9ca3af; }

/* Dependency chains */
.topo-chains { max-width: 1200px; }
.topo-chain {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px;
  padding: 4px 0; border-bottom: 1px solid #f1f5f9;
  font-size: 12px;
}
.topo-chain-node {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 2px 8px; border-radius: 4px; background: #f8fafc;
  font-family: monospace; font-size: 11px;
}
.topo-chain-core-switch { border-left: 3px solid #6366f1; }
.topo-chain-edge-router { border-left: 3px solid #10b981; }
.topo-chain-olt-gateway { border-left: 3px solid #f59e0b; }
.topo-chain-olt { border-left: 3px solid #8b5cf6; }
.topo-chain-health { font-size: 10px; }
.topo-chain-onus { font-size: 10px; color: #64748b; }
.topo-chain-arrow { color: #94a3b8; font-weight: 600; }

/* Device detail panel */
.topo-detail { max-width: 700px; }
.topo-detail-link {
  display: flex; align-items: center; gap: 6px;
  padding: 3px 0; font-size: 12px;
}
.topo-detail-if {
  display: flex; align-items: center; gap: 6px;
  padding: 2px 0; font-size: 11px; font-family: monospace;
}
.topo-if-status {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
}

/* ── Analysis tab ── */
.analysis-container { max-width: 900px; }

.analysis-severity {
  color: white;
  &--critical { background: #dc2626; }
  &--high { background: #ea580c; }
  &--medium { background: #d97706; }
  &--low { background: #16a34a; }
  &--unknown { background: #6b7280; }
}

.analysis-text { font-size: 14px; line-height: 1.6; color: var(--ops-text); white-space: pre-wrap; }

.analysis-chain {
  font-family: monospace; font-size: 12px; padding: 4px 8px;
  background: #f8fafc; border-radius: 4px; margin-bottom: 4px;
}

.analysis-action {
  padding: 6px 0; border-bottom: 1px solid #f1f5f9; font-size: 13px;
}

.analysis-device {
  padding: 4px 0; font-size: 13px; border-bottom: 1px solid #f8fafc;
  code { background: #f1f5f9; padding: 1px 6px; border-radius: 3px; font-size: 12px; }
}

.analysis-evidence {
  display: inline-block; font-size: 11px; color: #64748b; background: #f1f5f9;
  padding: 1px 8px; border-radius: 10px; margin: 2px 4px 2px 0;
}

.olt-health-card { min-width: 140px; border-radius: 8px; }
.olt-health--ok { border-left: 3px solid #22c55e; }
.olt-health--warning { border-left: 3px solid #f59e0b; }
.olt-health--critical { border-left: 3px solid #ef4444; }
.olt-health--unreachable { border-left: 3px solid #6b7280; background: #f9fafb; }

.analysis-raw {
  font-size: 11px; background: #1e293b; color: #e2e8f0; padding: 12px;
  border-radius: 6px; overflow-x: auto; max-height: 400px;
}

/* ── Network Map (Cytoscape) ── */
.netmap-container { display: flex; flex-direction: column; height: calc(100vh - 180px); }
.netmap-canvas {
  flex: 1; min-height: 400px; border: 1px solid var(--ops-border, #e2e8f0);
  border-radius: 8px; background: #fafbfc;
}
.netmap-detail { max-height: 300px; overflow-y: auto; }
.netmap-device { border-bottom: 1px solid #f1f5f9; }
.netmap-device--down { background: #fef2f2; }
</style>
