<template>
  <!-- Vue appareil moderne : carte réseau + voyants + tuiles + trafic + événements (le dense est replié plus bas) -->
  <div v-if="device" class="topo-summary">
    <div class="topo-row">
      <div class="topo-node"><q-icon name="public" size="22px" :color="status.online ? 'green-6' : 'grey-5'" /><div class="topo-cap">Internet</div></div>
      <div class="topo-link" :class="{ ok: status.online }"></div>
      <div class="topo-node"><q-icon name="cell_tower" size="22px" color="blue-grey-6" /><div class="topo-cap">OLT</div><div class="topo-sub">{{ oltLabel }}</div></div>
      <div class="topo-link" :class="{ ok: status.online }"></div>
      <div class="topo-node"><div class="topo-modem"><q-icon name="router" size="22px" color="primary" /><span class="topo-dot" :class="status.online ? 'on' : 'off'"></span></div><div class="topo-cap">Modem</div><div class="topo-sub" v-if="doc.olt_slot != null">{{ doc.olt_slot }}/{{ doc.olt_port }}/{{ doc.olt_ontid }}</div></div>
      <div class="topo-link" :class="{ ok: clientCount > 0 }"></div>
      <div class="topo-node topo-click" @click="openHosts"><div style="position:relative;display:inline-block;"><q-icon name="devices" size="22px" color="primary" /><q-circular-progress v-if="hostsLoading" indeterminate size="15px" :thickness="0.22" color="green-5" style="position:absolute;right:-9px;top:-4px;" /></div><div class="topo-cap">{{ clientCount }} app.</div></div>
    </div>
    <div class="topo-badges">
      <q-badge :color="statusBadgeColor">{{ status.label }}</q-badge>
      <q-badge v-if="device.opticalStatus" outline :color="device.opticalStatus === 'Up' ? 'green' : 'red'">Fibre {{ device.opticalStatus }}</q-badge>
      <q-badge v-if="signalLabel" outline :color="signalBadgeColor">Signal {{ signalLabel }}</q-badge>
      <span v-if="device.lastInform" class="text-caption text-grey-5">{{ timeAgo(device.lastInform) }}</span>
      <q-space />
      <q-btn flat dense size="sm" icon="sync" :loading="refreshing" @click="doRefresh" title="Rafraîchir" />
    </div>

    <div class="dv-grid">
      <div class="dv-tile" v-if="device.rxPower != null && device.rxPower !== 0"><div class="dv-tl"><q-icon name="network_check" size="14px" /> Signal Rx/Tx</div><div class="dv-tv" :style="{ color: rxColor }">{{ device.rxPower }}<span v-if="device.txPower != null"> / {{ device.txPower }}</span> dBm</div></div>
      <div class="dv-tile" v-if="device.uptime"><div class="dv-tl"><q-icon name="schedule" size="14px" /> Uptime</div><div class="dv-tv">{{ formatUptime(device.uptime) }}</div></div>
      <div class="dv-tile" v-if="managementIp"><div class="dv-tl"><q-icon name="settings" size="14px" /> Gestion</div><a :href="'https://' + managementIp + '/'" target="_blank" class="dv-tv dv-link">{{ managementIp }}</a></div>
      <div class="dv-tile" v-if="lastOutage"><div class="dv-tl dv-warn"><q-icon name="power_off" size="14px" /> Dern. coupure</div><div class="dv-tv">{{ lastOutage }}</div></div>
    </div>

    <div class="dv-row2">
      <div class="dv-wan">
        <div class="dv-tl">
          <q-icon name="show_chart" size="14px" /> {{ wanLive ? 'Débit WAN en direct' : 'Disponibilité & signal récents' }}
          <q-btn dense flat round size="9px" class="dv-wan-btn" :icon="wanLive ? 'stop' : 'speed'" :color="wanLive ? 'red' : 'primary'" @click="toggleWanLive">
            <q-tooltip>{{ wanLive ? 'Arrêter la mesure' : 'Débit WAN en direct — compteurs OLT (façon F)' }}</q-tooltip>
          </q-btn>
        </div>
        <!-- Mode LIVE : débit WAN temps réel (compteurs Counter64 de l'OLT, échantillon 5 s, façon F) -->
        <template v-if="wanLive">
          <div v-if="wanLiveErr" class="dv-wan-ph">{{ wanErrMsg }}</div>
          <template v-else>
            <svg viewBox="0 0 220 40" class="wan-spark" preserveAspectRatio="none">
              <polyline v-if="wanRates.length > 1" :points="wanLine('dl')" fill="none" stroke="#16a34a" stroke-width="1.5" />
              <polyline v-if="wanRates.length > 1" :points="wanLine('ul')" fill="none" stroke="#2563eb" stroke-width="1.5" />
            </svg>
            <div class="text-caption q-mt-xs">
              <span style="color:#16a34a;font-weight:600">▼ {{ fmtBps(wanNow && wanNow.dl) }}</span> &nbsp;
              <span style="color:#2563eb;font-weight:600">▲ {{ fmtBps(wanNow && wanNow.ul) }}</span>
              <q-spinner size="11px" class="q-ml-xs" v-if="!wanNow" /><span class="text-grey-5" v-if="!wanNow"> mesure…</span>
            </div>
          </template>
        </template>
        <!-- Mode défaut : bande disponibilité + signal Rx (historique device_cache, passif) -->
        <template v-else-if="wanHistory.length">
          <div class="wan-strip">
            <span v-for="(p, i) in wanHistory" :key="i" class="wan-tick" :style="{ background: p.online ? '#16a34a' : '#dc2626' }"><q-tooltip>{{ fmtTs(p.ts) }} — {{ p.online ? 'en ligne' : 'hors ligne' }}{{ p.rxPower != null ? ' · ' + p.rxPower + ' dBm' : '' }}</q-tooltip></span>
          </div>
          <div class="text-caption text-grey-6 q-mt-xs">Dispo {{ uptimePct }}% · Rx {{ lastRx }} · {{ wanHistory.length }} pts</div>
        </template>
        <div v-else class="dv-wan-ph"><q-icon name="insights" size="18px" color="grey-4" /> historique en accumulation</div>
      </div>
      <div class="dv-events" v-if="events.length">
        <div class="dv-tl"><q-icon name="history" size="14px" /> Événements récents</div>
        <div v-for="e in events.slice(0, 4)" :key="e.id" class="dv-ev"><q-icon :name="evIcon(e)" size="13px" :color="evColor(e)" /> <span class="text-grey-6">{{ evTime(e) }}</span> · {{ evLabel(e) }}</div>
      </div>
    </div>
  </div>

  <!-- Identité de l'appareil (repliée par défaut) -->
  <div class="collapse-head" @click="showIdentity = !showIdentity"><q-icon :name="showIdentity ? 'expand_more' : 'chevron_right'" size="18px" class="q-mr-xs" />Identité de l'appareil</div>
  <div v-show="showIdentity || !device" class="modal-field-grid">
    <div v-for="f in equipFields" :key="f.field" class="mf">
      <span class="mf-label">{{ f.label }}</span>
      <InlineField :value="doc[f.field]" :field="f.field" doctype="Service Equipment" :docname="docName"
        v-bind="f.props || {}" :placeholder="f.placeholder || '—'" @saved="v => doc[f.field] = v.value" />
    </div>
    <div class="mf" v-if="doc.barcode"><span class="mf-label">Code-barres</span><code>{{ doc.barcode }}</code></div>
  </div>

  <div v-if="device" class="q-mt-md">
    <div class="collapse-head" @click="showTech = !showTech"><q-icon :name="showTech ? 'expand_more' : 'chevron_right'" size="18px" class="q-mr-xs" />Détails techniques<span class="text-caption text-grey-5 q-ml-xs">fibre · IP · WiFi · Ethernet · firmware</span></div>
    <div v-show="showTech">
    <div class="diag-section fibre-top">
      <q-icon name="settings_input_hdmi" size="16px" class="q-mr-xs" />
      <span class="text-weight-bold q-mr-sm">Fibre</span>
      <q-badge :color="device.opticalStatus === 'Up' ? 'green' : 'red'" class="q-mr-sm">{{ device.opticalStatus || '—' }}</q-badge>
      <template v-if="device.rxPower != null && device.rxPower !== 0">
        <span class="diag-label q-mr-xs">Rx</span>
        <span class="text-weight-medium" :style="{ color: rxColor }">{{ device.rxPower }} dBm</span>
      </template>
      <template v-if="device.txPower != null && device.txPower !== 0">
        <span class="diag-label q-ml-sm q-mr-xs">Tx</span>
        <span class="text-weight-medium">{{ device.txPower }} dBm</span>
      </template>
    </div>

    <div class="diag-section" v-if="device.interfaces?.length">
      <div class="diag-section-title"><q-icon name="language" size="16px" class="q-mr-xs" />Adresses IP</div>
      <div class="ip-grid">
        <div v-for="iface in sortedInterfaces" :key="iface.iface + iface.ip" class="ip-item">
          <span class="ip-role" :class="'ip-role--' + iface.role">{{ roleLabel(iface.role) }}</span>
          <a v-if="iface.role === 'management'" :href="'https://' + iface.ip + '/superadmin/'" target="_blank" class="ip-addr ip-link">{{ iface.ip }}</a>
          <code v-else class="ip-addr">{{ iface.ip }}</code>
          <span class="text-caption text-grey-5" v-if="iface.name">({{ iface.name }})</span>
        </div>
      </div>
    </div>

    <div class="diag-section" v-if="device.wifi">
      <div class="diag-section-title"><q-icon name="wifi" size="16px" class="q-mr-xs" />WiFi</div>
      <div class="diag-grid">
        <template v-for="(radio, key) in wifiRadios" :key="key">
          <div class="diag-item" v-if="radio.status">
            <span class="diag-label">{{ radio.label }}</span>
            <span :class="radio.status === 'Up' ? 'text-positive' : 'text-negative'">
              {{ radio.status }} &middot; Ch {{ radio.channel || '?' }}
              <template v-if="radio.bandwidth"> &middot; {{ radio.bandwidth }}</template>
            </span>
          </div>
          <div class="diag-item" v-if="radio.status">
            <span class="diag-label">{{ radio.label }} clients</span>
            <span class="text-weight-medium">{{ radio.clients }}</span>
          </div>
        </template>
        <div class="diag-item" v-if="device.wifi.totalClients > 0">
          <span class="diag-label">Total clients</span>
          <span class="text-weight-bold">
            {{ device.wifi.totalClients }}
            <span v-if="device.wifi.meshClients > 0" class="text-caption text-grey-6">
              ({{ device.wifi.directClients }} direct + {{ device.wifi.meshClients }} mesh)
            </span>
          </span>
        </div>
      </div>
    </div>

    <div class="diag-section" v-if="device.mesh?.length > 1 && !hosts">
      <div class="diag-section-title"><q-icon name="hub" size="16px" class="q-mr-xs" />EasyMesh ({{ device.mesh.length }} noeuds)</div>
      <div class="mesh-nodes">
        <div v-for="node in device.mesh" :key="node.id" class="mesh-node">
          <div class="mesh-node-header">
            <q-badge :color="node.active ? 'green' : 'red'" class="q-mr-xs" style="padding: 2px 5px;" />
            <span class="text-weight-medium">{{ node.name || 'Node ' + node.id }}</span>
            <span class="text-caption text-grey-6 q-ml-xs">{{ node.clients }} clients</span>
          </div>
          <div class="text-caption text-grey-5" v-if="node.ip || node.mac">
            <span v-if="node.ip">{{ node.ip }}</span>
            <span v-if="node.mac"> &middot; {{ node.mac }}</span>
          </div>
        </div>
      </div>
    </div>

    <div class="diag-section" v-if="device.ethernet">
      <div class="diag-section-title"><q-icon name="cable" size="16px" class="q-mr-xs" />Ethernet</div>
      <div class="diag-grid">
        <div class="diag-item" v-for="(port, key) in ethernetPorts" :key="key">
          <span class="diag-label">{{ port.label }}</span>
          <span :class="port.status === 'Up' ? 'text-positive' : 'text-grey-6'">
            {{ port.status || '—' }}
            <template v-if="port.speed > 0"> &middot; {{ port.speed }} Mbps</template>
          </span>
        </div>
      </div>
    </div>

    <div class="diag-section">
      <div class="diag-grid">
        <div class="diag-item" v-if="device.firmware"><span class="diag-label">Firmware ACS</span><code class="text-caption">{{ device.firmware }}</code></div>
        <div class="diag-item" v-if="device.ssid"><span class="diag-label">SSID</span>{{ device.ssid }}</div>
        <div class="diag-item" v-if="device.uptime"><span class="diag-label">Uptime</span>{{ formatUptime(device.uptime) }}</div>
      </div>
    </div>
    </div>

    <div class="diag-section">
      <div class="diag-section-title collapse-head" @click="showHosts = !showHosts">
        <q-icon :name="showHosts ? 'expand_more' : 'chevron_right'" size="18px" class="q-mr-xs" />
        <q-icon name="devices" size="16px" class="q-mr-xs" />
        Appareils connectés
        <span v-if="hosts" class="text-caption text-grey-5 q-ml-xs">({{ hosts.total }})</span>
        <q-btn flat dense size="sm" icon="refresh" class="q-ml-sm" :loading="hostsLoading" @click.stop="loadHosts(true)" title="Rafraichir la liste" />
      </div>
      <div v-show="showHosts">
      <div v-if="hostsLoading" class="text-caption text-grey-5 q-py-xs">
        <q-spinner size="14px" class="q-mr-xs" /> Interrogation de l'equipement...
      </div>
      <div v-else-if="hosts?.hosts.length">
        <div v-for="group in groupedHosts" :key="group.nodeId" class="node-group">
          <div class="node-group-header" @click="toggleNode(group.nodeId)">
            <q-icon :name="expandedNodes[group.nodeId] ? 'expand_more' : 'chevron_right'" size="18px" class="node-chevron" />
            <q-badge :color="group.active ? 'green' : 'red'" class="q-mr-xs" style="padding: 2px 5px;" />
            <q-icon :name="group.nodeId === '_wired' ? 'cable' : 'router'" size="14px" class="q-mr-xs" :color="group.nodeId === '_wired' ? 'grey-6' : 'blue-6'" />
            <span class="text-weight-medium">{{ group.label }}</span>
            <q-badge :color="group.active ? 'blue-1' : 'grey-3'" :text-color="group.active ? 'blue-8' : 'grey-6'" class="q-ml-xs" style="font-size:0.7rem;">{{ group.clients.length }}</q-badge>
            <span class="text-caption text-grey-5 q-ml-auto" v-if="group.ip">{{ group.ip }}</span>
            <span class="text-caption text-grey-5 q-ml-xs" v-if="group.mac">&middot; {{ group.mac }}</span>
          </div>
          <div v-show="expandedNodes[group.nodeId]">
            <table class="hosts-table">
              <thead><tr><th>Signal</th><th>Nom</th><th>IP</th><th>MAC</th><th>Connexion</th><th>Bail</th></tr></thead>
              <tbody>
                <tr v-for="h in group.clients" :key="h.id" :class="{ 'text-grey-5': !h.active }">
                  <td class="text-caption">
                    <span v-if="h.signal != null" :style="{ color: signalColor(h.signal) }">
                      <q-icon name="signal_cellular_alt" size="12px" :style="{ color: signalColor(h.signal) }" />
                      {{ formatSignal(h.signal) }}
                    </span>
                    <span v-else>—</span>
                  </td>
                  <td class="text-weight-medium">{{ h.name || '—' }}</td>
                  <td><code class="text-caption">{{ h.ip || '—' }}</code></td>
                  <td><code class="text-caption">{{ h.mac || '—' }}</code></td>
                  <td class="text-caption">{{ h.band || h.connType || '—' }}</td>
                  <td class="text-caption">{{ h.leaseRemaining != null ? formatLease(h.leaseRemaining) : '—' }}</td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>
      </div>
      <div v-else-if="hosts" class="text-caption text-grey-5 q-py-xs">Aucun appareil</div>
      </div>
    </div>
  </div>
  <div v-else-if="doc.serial_number && !device" class="q-mt-md text-caption text-grey-5">
    <q-spinner size="14px" class="q-mr-xs" v-if="deviceLoading" /> Chargement diagnostic ACS...
  </div>

  <!-- ── Diagnostic modem avancé (modem-bridge / Playwright) — DÉSACTIVÉ « pour le moment » : trop lourd (~15 s).
       Réactiver : remettre v-if="managementIp || props.doc?.serial_number". Le diagnostic GenieACS/OLT + liste d'appareils suffisent. ── -->
  <div v-if="false" class="q-mt-md">
    <div class="info-block-title">
      <q-icon name="wifi_find" size="16px" class="q-mr-xs" />
      Diagnostic modem
      <q-badge v-if="wifiDiag?.modemType" :label="modemTypeLabel(wifiDiag.modemType)" class="q-ml-xs" :color="modemTypeColor(wifiDiag.modemType)" dense />
      <q-btn flat dense size="sm" :icon="wifiDiag ? 'refresh' : 'play_arrow'" class="q-ml-sm"
        :label="wifiDiag ? 'Relancer' : 'Diagnostic'" :loading="wifiDiagLoading"
        @click="runWifiDiagnostic" color="primary" />
      <span v-if="wifiDiag" class="text-caption text-grey-5 q-ml-sm">{{ wifiDiag.durationMs }}ms</span>
    </div>

    <div v-if="wifiDiagLoading && !wifiDiag" class="text-caption text-grey-5 q-py-sm">
      <q-spinner size="14px" class="q-mr-xs" /> Connexion au modem via Playwright (~15s)...
    </div>
    <div v-if="wifiDiagError" class="text-caption text-negative q-py-xs">{{ wifiDiagError }}</div>

    <template v-if="wifiDiag">
      <!-- Wired equipment detection banner -->
      <div v-if="wifiDiag.wiredEquipment?.length" class="adv-issue adv-issue--info q-mb-sm">
        <div class="adv-issue-header">
          <q-icon name="router" size="16px" />
          <span class="text-weight-bold">{{ wifiDiag.wiredEquipment.length }} repeteur(s) mesh detecte(s)</span>
        </div>
        <div v-for="eq in wifiDiag.wiredEquipment" :key="eq.mac" class="q-ml-md q-mt-xs text-caption">
          <q-icon name="router" size="12px" class="q-mr-xs" color="info" />
          <span class="text-weight-medium">{{ eq.hostname || eq.model }}</span>
          &mdash; {{ eq.ip }} ({{ eq.mac }})
          <q-badge :color="eq.type === 'mesh_repeater' ? 'blue-2' : 'orange-2'" :text-color="eq.type === 'mesh_repeater' ? 'blue-9' : 'orange-9'" class="q-ml-xs" style="font-size:0.6rem;">
            {{ eq.type === 'mesh_repeater' ? 'Confirme' : 'Probable' }}
          </q-badge>
        </div>
      </div>

      <!-- Ethernet ports (enhanced) -->
      <div v-if="wifiDiag.ethernetPorts?.length" class="diag-section">
        <div class="diag-section-title"><q-icon name="cable" size="16px" class="q-mr-xs" />Ports Ethernet</div>
        <div class="diag-grid">
          <div v-for="port in wifiDiag.ethernetPorts" :key="port.port" class="diag-item">
            <span class="diag-label">{{ port.label }}</span>
            <span :class="port.status === 'Up' ? 'text-positive' : 'text-grey-6'">
              {{ port.status || '—' }}
              <template v-if="port.speed > 0"> &middot; {{ port.speed }} Mbps</template>
              <template v-if="port.stats && port.status === 'Up'">
                &middot; {{ formatBytes(port.stats.txBytes) }}↑ {{ formatBytes(port.stats.rxBytes) }}↓
              </template>
            </span>
          </div>
        </div>
      </div>

      <!-- DHCP Leases -->
      <div v-if="wifiDiag.dhcpLeases?.length" class="diag-section">
        <div class="diag-section-title">
          <q-icon name="dns" size="16px" class="q-mr-xs" />
          Baux DHCP ({{ wifiDiag.dhcpLeases.length }})
        </div>
        <table class="hosts-table">
          <thead><tr><th>Nom</th><th>IP</th><th>MAC</th><th>Bail</th><th>Type</th></tr></thead>
          <tbody>
            <tr v-for="l in wifiDiag.dhcpLeases" :key="l.mac">
              <td class="text-weight-medium">{{ l.hostname || '—' }}</td>
              <td><code class="text-caption">{{ l.ip }}</code></td>
              <td><code class="text-caption">{{ l.mac }}</code></td>
              <td class="text-caption">{{ l.expiry != null ? formatLease(l.expiry) : '—' }}</td>
              <td>
                <q-badge v-if="l.isMeshRepeater" color="blue-1" text-color="blue-9" style="font-size:0.6rem;">Mesh</q-badge>
                <q-badge v-else-if="l.isWired" color="grey-3" text-color="grey-8" style="font-size:0.6rem;">Filaire</q-badge>
                <q-badge v-else color="green-2" text-color="green-9" style="font-size:0.6rem;">WiFi</q-badge>
              </td>
            </tr>
          </tbody>
        </table>
      </div>

      <!-- Issues panel -->
      <div v-if="wifiDiag.issues.length" class="adv-issues q-mb-sm">
        <div v-for="(issue, i) in wifiDiag.issues" :key="i" class="adv-issue" :class="'adv-issue--' + issue.severity">
          <div class="adv-issue-header">
            <q-icon :name="issue.severity === 'critical' ? 'error' : issue.severity === 'warning' ? 'warning' : 'info'" size="16px" />
            <span class="text-weight-bold">{{ issue.message }}</span>
          </div>
          <div class="adv-issue-detail">{{ issue.detail }}</div>
          <div class="adv-issue-action"><q-icon name="lightbulb" size="12px" class="q-mr-xs" />{{ issue.action }}</div>
        </div>
      </div>
      <div v-else class="adv-ok q-mb-sm">
        <q-icon name="check_circle" size="16px" color="positive" class="q-mr-xs" />
        Aucun probleme detecte — reseau en bon etat.
      </div>

      <!-- WAN IPs -->
      <div v-if="wifiDiag.wanIPs.length" class="diag-section">
        <div class="diag-section-title"><q-icon name="public" size="16px" class="q-mr-xs" />Adresses WAN (ONU)</div>
        <div class="ip-grid">
          <div v-for="w in wifiDiag.wanIPs" :key="w.ip" class="ip-item">
            <span class="ip-role" :class="'ip-role--' + w.role">{{ wanRoleLabel(w.role) }}</span>
            <code class="ip-addr">{{ w.ip }}</code>
            <span class="text-caption text-grey-5">/{{ maskToCidr(w.mask) }} {{ w.type }}</span>
          </div>
        </div>
      </div>

      <!-- Mesh topology -->
      <div v-if="wifiDiag.meshNodes.length > 1" class="diag-section">
        <div class="diag-section-title"><q-icon name="hub" size="16px" class="q-mr-xs" />Topologie Mesh ({{ wifiDiag.meshNodes.length }} noeuds)</div>
        <div class="adv-mesh-nodes">
          <div v-for="node in wifiDiag.meshNodes" :key="node.mac" class="adv-mesh-node">
            <div class="adv-mesh-header">
              <q-badge :color="node.active ? 'green' : 'red'" style="padding: 2px 5px;" />
              <span class="text-weight-bold q-ml-xs">{{ node.hostname }}</span>
              <span class="text-caption text-grey-6 q-ml-xs">{{ node.model }}</span>
              <q-badge v-if="node.isController" outline color="info" class="q-ml-xs" style="font-size:0.6rem;">Routeur</q-badge>
            </div>
            <div class="adv-mesh-stats">
              <span v-if="!node.isController" class="adv-mesh-stat">
                <span class="diag-label">Backhaul</span>
                <span :style="{ color: backhaulColor(node.backhaul.signal) }">
                  <q-icon name="signal_cellular_alt" size="12px" /> {{ node.backhaul.signal }}/255
                </span>
                &middot; {{ node.backhaul.linkRate }} Mbps
                &middot; {{ node.backhaul.utilization }}% util.
              </span>
              <span class="adv-mesh-stat"><span class="diag-label">CPU</span> {{ node.cpu }}%</span>
              <span class="adv-mesh-stat"><span class="diag-label">Uptime</span> {{ formatUptime(node.uptime) }}</span>
              <span v-if="node.speedDown || node.speedUp" class="adv-mesh-stat">
                <span class="diag-label">Debit</span> ↓{{ node.speedDown }} ↑{{ node.speedUp }} Mbps
              </span>
            </div>
          </div>
        </div>
      </div>

      <!-- Radios -->
      <div v-if="wifiDiag.radios.length" class="diag-section">
        <div class="diag-section-title"><q-icon name="settings_input_antenna" size="16px" class="q-mr-xs" />Radios</div>
        <div class="diag-grid">
          <template v-for="r in wifiDiag.radios" :key="r.band">
            <div class="diag-item">
              <span class="diag-label">{{ r.band }}</span>
              <span :class="r.status === 'Up' ? 'text-positive' : 'text-negative'">
                Ch {{ r.channel }} &middot; {{ r.bandwidth }} &middot; {{ r.standard }}
              </span>
            </div>
            <div class="diag-item">
              <span class="diag-label">{{ r.band }} config</span>
              <span>Puissance {{ r.txPower }}% &middot; Canal {{ r.autoChannel ? 'auto' : 'fixe' }}</span>
            </div>
          </template>
        </div>
      </div>

      <!-- Connected clients -->
      <div v-if="wifiDiag.clients.length" class="diag-section">
        <div class="diag-section-title">
          <q-icon name="devices" size="16px" class="q-mr-xs" />
          Clients WiFi ({{ wifiDiag.clients.filter(c => c.active).length }} connectes
          <span v-if="wifiDiag.clients.some(c => !c.active)" class="text-caption text-grey-5">
            / {{ wifiDiag.clients.filter(c => !c.active).length }} hors ligne
          </span>)
        </div>
        <table class="hosts-table adv-clients-table">
          <thead>
            <tr>
              <th>Signal</th>
              <th>Nom</th>
              <th>Noeud</th>
              <th>Bande</th>
              <th>Debit ↓/↑</th>
              <th>Perte</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="c in wifiDiag.clients" :key="c.mac" :class="{ 'text-grey-5 adv-inactive-row': !c.active }">
              <td>
                <template v-if="c.active">
                  <div class="adv-signal-bar">
                    <div class="adv-signal-fill" :style="{ width: signalPercent(c.signal) + '%', background: signalColor(c.signal) }" />
                  </div>
                  <span class="text-caption" :style="{ color: signalColor(c.signal) }">{{ c.signal > 0 ? c.signal : '?' }}</span>
                </template>
                <q-icon v-else name="wifi_off" size="14px" color="grey-4" />
              </td>
              <td>
                <span class="text-weight-medium">{{ c.hostname || '—' }}</span>
                <div class="text-caption text-grey-5">{{ c.ip || 'hors ligne' }}</div>
              </td>
              <td class="text-caption">{{ c.meshNode || '—' }}</td>
              <td class="text-caption">
                <q-badge :color="c.band === '5GHz' ? 'green-2' : c.band === '2.4GHz' ? 'blue-2' : 'grey-3'"
                  :text-color="c.band === '5GHz' ? 'green-9' : c.band === '2.4GHz' ? 'blue-9' : 'grey-7'"
                  style="font-size: 0.65rem;">{{ c.band || c.standard || '?' }}</q-badge>
              </td>
              <td class="text-caption">
                {{ Math.round(c.linkDown / 1000) }}/{{ Math.round(c.linkUp / 1000) }} Mbps
              </td>
              <td>
                <span v-if="c.lossPercent > 10" class="text-negative text-weight-bold">{{ c.lossPercent }}%</span>
                <span v-else-if="c.lossPercent > 5" class="text-warning text-weight-bold">{{ c.lossPercent }}%</span>
                <span v-else-if="c.lossPercent > 0" class="text-caption text-grey-6">{{ c.lossPercent }}%</span>
                <span v-else class="text-caption text-grey-5">—</span>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </template>
  </div>

  <div v-if="doc.olt_name" class="q-mt-md">
    <div class="info-block-title">Information OLT</div>
    <div class="modal-field-grid">
      <div class="mf"><span class="mf-label">OLT</span>{{ doc.olt_name }}</div>
      <div class="mf"><span class="mf-label">IP OLT</span><code>{{ doc.olt_ip }}</code></div>
      <div class="mf"><span class="mf-label">Slot / Port</span>{{ doc.olt_slot }} / {{ doc.olt_port }}</div>
      <div class="mf"><span class="mf-label">ONT ID</span>{{ doc.olt_ontid }}</div>
    </div>
    <div class="mf" v-if="doc.installation_date"><span class="mf-label">Installe le</span>{{ doc.installation_date }}</div>
    <div class="mf" v-if="doc.warranty_end"><span class="mf-label">Fin garantie</span>{{ doc.warranty_end }}</div>
    <div class="mf" v-if="doc.service_location"><span class="mf-label">Adresse</span>{{ doc.service_location }}</div>
    <div class="mf" v-if="doc.subscription"><span class="mf-label">Abonnement</span>{{ doc.subscription }}</div>
  </div>

  <!-- Actions cycle-de-vie ONU (plan→confirm→run : suspendre/rétablir/redémarrer/vitesse/remplacer/retirer) — remplace le
       « Do Stuff » de F. GATÉ manage_settings (écritures réseau : suspendre coupe le client). Bloc AUTONOME (n'exige pas olt_name). -->
  <div v-if="doc.serial_number && doc.olt_ip && can('manage_settings')" class="q-mt-md">
    <div class="info-block-title">Actions appareil (réseau)</div>
    <OnuActionsPanel :serial="doc.serial_number" :olt="doc.olt_ip" :status="doc.status" @changed="onOnuChanged" />
  </div>

  <!-- Port Neighbor Context — shows health of the same OLT port -->
  <div v-if="portCtx && portCtx.total > 0" class="q-mt-md">
    <div class="info-block-title">
      <q-icon name="hub" size="16px" class="q-mr-xs" />
      Voisinage port
      <q-badge v-if="portCtx.is_mass_outage" color="red" class="q-ml-sm">Panne secteur</q-badge>
      <q-badge v-else-if="portCtx.is_warning" color="orange" class="q-ml-sm">Alerte</q-badge>
      <q-badge v-else color="green" class="q-ml-sm">Normal</q-badge>
    </div>
    <div class="port-ctx-bar q-mt-xs">
      <div class="port-ctx-fill port-ctx-fill--online" :style="{ width: (portCtx.online / portCtx.total * 100) + '%' }" />
      <div class="port-ctx-fill port-ctx-fill--offline" :style="{ width: (portCtx.offline / portCtx.total * 100) + '%' }" />
    </div>
    <div class="port-ctx-legend text-caption q-mt-xs">
      <span class="text-positive text-weight-bold">{{ portCtx.online }}</span> en ligne
      <span v-if="portCtx.offline > 0" class="q-ml-md text-negative text-weight-bold">{{ portCtx.offline }}</span>
      <span v-if="portCtx.offline > 0"> hors ligne</span>
      <span class="text-grey-6 q-ml-md">/ {{ portCtx.total }} sur le port</span>
    </div>
    <div v-if="portCtx.offline > 0" class="port-ctx-neighbors q-mt-xs">
      <div v-for="n in portCtx.neighbors.filter(x => !x.is_self && x.status !== 'online')" :key="n.serial_prefix"
        class="port-ctx-neighbor text-caption">
        <q-icon name="circle" size="8px" color="red" class="q-mr-xs" />
        {{ n.serial_prefix }} — {{ n.cause || 'inconnu' }}
        <span v-if="n.distance_m" class="text-grey-5 q-ml-xs">{{ n.distance_m }}m</span>
      </div>
    </div>
  </div>
  <div class="q-mt-md">
    <div class="info-block-title">Acces distant</div>
    <div class="info-row q-py-xs"><span class="mf-label">Utilisateur</span>
      <InlineField :value="doc.login_user" field="login_user" doctype="Service Equipment" :docname="docName"
        placeholder="—" @saved="v => doc.login_user = v.value" />
    </div>
  </div>
  <div class="q-mt-md">
    <div class="info-block-title">Notes</div>
    <InlineField :value="doc.notes" field="notes" doctype="Service Equipment" :docname="docName"
      type="textarea" placeholder="Ajouter des notes..." @saved="v => doc.notes = v.value" />
  </div>
  <div v-if="doc.move_log?.length" class="q-mt-md">
    <div class="info-block-title">Historique ({{ doc.move_log.length }})</div>
    <div v-for="m in doc.move_log" :key="m.idx" class="info-row q-py-xs text-caption">
      {{ m.date }} --- {{ m.from_location || '?' }} &rarr; {{ m.to_location || '?' }} {{ m.reason ? '(' + m.reason + ')' : '' }}
    </div>
  </div>

  <div v-if="can('manage_settings')" class="q-mt-lg q-pt-md" style="border-top: 1px solid #f1f5f9">
    <q-btn flat dense no-caps color="negative" icon="delete_outline" label="Supprimer cet équipement"
      @click="confirmDelete" :loading="deleting" class="text-caption" />
  </div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useQuasar } from 'quasar'
import InlineField from 'src/components/shared/InlineField.vue'
import OnuActionsPanel from './modules/OnuActionsPanel.vue'
import { usePermissions } from 'src/composables/usePermissions'
import { useDeviceStatus } from 'src/composables/useDeviceStatus'
import { useModemDiagnostic } from 'src/composables/useModemDiagnostic'
import { deleteDoc } from 'src/api/erp'
import { HUB_URL } from 'src/config/hub'

const props = defineProps({ doc: { type: Object, required: true }, docName: String })
const emit = defineEmits(['deleted'])

const $q = useQuasar()
const { can } = usePermissions() // écritures réseau ONU + suppression = gated (manage_settings)
const { fetchStatus, fetchOltStatus, fetchPortContext, getDevice, isOnline, combinedStatus, signalQuality, refreshDeviceParams, fetchHosts, loading: deviceLoading } = useDeviceStatus()
const { fetchDiagnostic, fetchDiagnosticAuto, loading: wifiDiagLoading, error: wifiDiagError, data: wifiDiag } = useModemDiagnostic()
const refreshing = ref(false)
const deleting = ref(false)
const portCtx = ref(null)

const managementIp = computed(() => {
  const iface = device.value?.interfaces?.find(i => i.role === 'management')
  return iface?.ip || null
})

async function runWifiDiagnostic() {
  const serial = props.doc?.serial_number
  // Try auto-fetch first (credentials resolved server-side)
  if (serial) {
    const result = await fetchDiagnosticAuto(serial)
    if (result) return
  }
  // Fallback: manual password entry on any auto-fetch failure
  const ip = managementIp.value || props.doc?.ip_address
  if (!ip) return
  const errMsg = wifiDiagError.value || ''
  $q.dialog({
    title: 'Mot de passe modem',
    message: errMsg
      ? `Echec auto (${errMsg.substring(0, 80)}) — entrer le mot de passe pour ${ip}`
      : `Entrer le mot de passe superadmin pour ${ip}`,
    prompt: { model: '', type: 'password', filled: true },
    cancel: { flat: true, label: 'Annuler' },
    ok: { label: 'Lancer', color: 'primary' },
    persistent: false,
  }).onOk(pass => {
    if (pass) fetchDiagnostic(ip, pass)
  })
}

const MODEM_TYPE_LABELS = {
  tplink_xx230v: 'TP-Link',
  raisecom_boa: 'Raisecom 803-W',
  raisecom_php: 'Raisecom 803-WS2',
}
const MODEM_TYPE_COLORS = {
  tplink_xx230v: 'teal',
  raisecom_boa: 'orange',
  raisecom_php: 'deep-purple',
}
const modemTypeLabel = (type) => MODEM_TYPE_LABELS[type] || type
const modemTypeColor = (type) => MODEM_TYPE_COLORS[type] || 'grey'

const wanRoleLabel = (role) => ROLE_LABELS[role] || role

function maskToCidr(mask) {
  if (!mask) return '?'
  return mask.split('.').reduce((c, o) => c + (parseInt(o) >>> 0).toString(2).replace(/0/g, '').length, 0)
}

function signalPercent(raw) {
  if (!raw || raw <= 0) return 0
  return raw >= 0 ? Math.round((raw / 255) * 100) : Math.round(Math.max(0, Math.min(100, ((raw + 90) / 60) * 100)))
}

function backhaulColor(signal) {
  if (signal >= 100) return '#4ade80'
  if (signal >= 80) return '#a3e635'
  if (signal >= 60) return '#fbbf24'
  return '#f87171'
}

function formatBytes(bytes) {
  if (!bytes || bytes <= 0) return '0 B'
  const units = ['B', 'KB', 'MB', 'GB']
  const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1)
  return (bytes / Math.pow(1024, i)).toFixed(i > 0 ? 1 : 0) + ' ' + units[i]
}

function confirmDelete () {
  $q.dialog({
    title: 'Supprimer cet équipement ?',
    message: `${props.doc.serial_number || props.docName} sera supprimé définitivement.`,
    cancel: { flat: true, label: 'Annuler' },
    ok: { color: 'negative', label: 'Supprimer', flat: true },
    persistent: true,
  }).onOk(async () => {
    deleting.value = true
    try {
      await deleteDoc('Service Equipment', props.docName)
      $q.notify({ type: 'positive', message: 'Équipement supprimé' })
      emit('deleted', props.docName)
    } catch (e) {
      $q.notify({ type: 'negative', message: 'Erreur: ' + e.message })
    } finally {
      deleting.value = false
    }
  })
}
const hosts = ref(null)
const hostsLoading = ref(false)
const expandedNodes = ref({})

const equipFields = [
  { field: 'equipment_type', label: 'Type', props: { type: 'select', options: ['ONT', 'Router', 'Switch', 'AP', 'OLT', 'Décodeur', 'Modem', 'Autre'] } },
  { field: 'status', label: 'Statut', props: { type: 'select', options: ['Active', 'Inactive', 'En stock', 'Défectueux', 'Retourné'] } },
  { field: 'brand', label: 'Marque' },
  { field: 'model', label: 'Modele' },
  { field: 'serial_number', label: 'N serie' },
  { field: 'mac_address', label: 'MAC' },
  { field: 'ip_address', label: 'IP' },
  { field: 'firmware_version', label: 'Firmware' },
  { field: 'ownership', label: 'Propriete', props: { type: 'select', options: ['Client', 'Compagnie', 'Location'] } },
]

const toggleNode = (nodeId) => { expandedNodes.value[nodeId] = !expandedNodes.value[nodeId] }
const device = computed(() => props.doc.serial_number ? getDevice(props.doc.serial_number) : null)
const online = computed(() => isOnline(props.doc.serial_number))
const status = computed(() => combinedStatus(props.doc.serial_number))
const statusBadgeColor = computed(() => {
  if (status.value.source === 'unknown') return 'amber'
  return status.value.online === true ? 'green' : status.value.online === false ? 'red' : 'grey'
})

const rxColor = computed(() => {
  const q = signalQuality(props.doc.serial_number)
  return q === 'excellent' ? '#4ade80' : q === 'good' ? '#a3e635' : q === 'fair' ? '#fbbf24' : '#f87171'
})

const sortedInterfaces = computed(() => {
  if (!device.value?.interfaces) return []
  const order = { internet: 0, management: 1, service: 2, lan: 3, unknown: 4 }
  return [...device.value.interfaces].sort((a, b) => (order[a.role] ?? 9) - (order[b.role] ?? 9))
})

const ROLE_LABELS = { internet: 'Internet', management: 'Gestion', service: 'Service', lan: 'LAN', unknown: '?' }
const roleLabel = (role) => ROLE_LABELS[role] || role

const wifiRadios = computed(() => {
  if (!device.value?.wifi) return []
  const w = device.value.wifi
  const radios = []
  if (w.radio1?.status) radios.push({ ...w.radio1, label: '2.4 GHz' })
  if (w.radio2?.status) radios.push({ ...w.radio2, label: '5 GHz' })
  if (w.radio3?.status) radios.push({ ...w.radio3, label: '6 GHz' })
  return radios
})

const ethernetPorts = computed(() => {
  if (!device.value?.ethernet) return []
  const e = device.value.ethernet, ports = []
  if (e.port1) ports.push({ ...e.port1, label: 'Port 1' })
  if (e.port2) ports.push({ ...e.port2, label: 'Port 2' })
  return ports
})

const groupedHosts = computed(() => {
  if (!hosts.value?.hosts) return []
  const meshNodes = device.value?.mesh || []
  const groups = new Map()
  for (const node of meshNodes) {
    groups.set(node.name || node.id, { nodeId: node.name || node.id, label: node.name || 'Node ' + node.id, active: node.active, ip: node.ip, mac: node.mac, clients: [] })
  }
  const wiredGroup = { nodeId: '_wired', label: 'Filaire / Autre', active: true, ip: null, mac: null, clients: [] }
  for (const h of hosts.value.hosts) {
    if (h.isMeshNode) continue
    const nodeName = h.attachedNode
    if (nodeName && groups.has(nodeName)) groups.get(nodeName).clients.push(h)
    else if (nodeName) groups.set(nodeName, { nodeId: nodeName, label: nodeName, active: true, ip: null, mac: null, clients: [h] })
    else wiredGroup.clients.push(h)
  }
  const result = [...groups.values()]
  if (wiredGroup.clients.length) result.push(wiredGroup)
  return result
})

const SIGNAL_STEPS = [
  { pct: 0, color: 'hsl(0, 100%, 40%)' }, { pct: 10, color: 'hsl(6, 100%, 42%)' },
  { pct: 20, color: 'hsl(12, 100%, 44%)' }, { pct: 30, color: 'hsl(20, 95%, 45%)' },
  { pct: 40, color: 'hsl(30, 100%, 45%)' }, { pct: 50, color: 'hsl(42, 100%, 45%)' },
  { pct: 60, color: 'hsl(54, 100%, 40%)' }, { pct: 70, color: 'hsl(72, 90%, 38%)' },
  { pct: 80, color: 'hsl(90, 80%, 36%)' }, { pct: 90, color: 'hsl(105, 75%, 35%)' },
  { pct: 100, color: 'hsl(120, 70%, 35%)' },
]

function signalColor (raw) {
  if (raw == null) return '#9ca3af'
  const val = parseFloat(raw)
  if (isNaN(val)) return '#9ca3af'
  const pct = val >= 0 ? Math.round((val / 255) * 100) : Math.round(Math.max(0, Math.min(100, ((val + 90) / 60) * 100)))
  return SIGNAL_STEPS[Math.min(10, Math.max(0, Math.floor(pct / 10)))].color
}

function formatSignal (raw) {
  if (raw == null) return null
  const val = parseFloat(raw)
  return isNaN(val) ? null : val >= 0 ? Math.round((val / 255) * 100) + '%' : val + ' dBm'
}

function timeAgo (dateStr) {
  if (!dateStr) return ''
  const mins = Math.floor((Date.now() - new Date(dateStr).getTime()) / 60000)
  if (mins < 1) return "a l'instant"
  if (mins < 60) return `il y a ${mins}m`
  const hrs = Math.floor(mins / 60)
  return hrs < 24 ? `il y a ${hrs}h` : `il y a ${Math.floor(hrs / 24)}j`
}

function formatLease (seconds) {
  if (seconds == null || seconds <= 0) return 'expire'
  const h = Math.floor(seconds / 3600), m = Math.floor((seconds % 3600) / 60)
  return h > 0 ? `${h}h${m > 0 ? m + 'm' : ''}` : `${m}m`
}

async function loadHosts (refresh = false) {
  if (!props.doc.serial_number) return
  hostsLoading.value = true
  try {
    hosts.value = await fetchHosts(props.doc.serial_number, refresh)
    if (hosts.value?.hosts) {
      const nodes = new Set()
      for (const h of hosts.value.hosts) nodes.add(h.attachedNode || '_wired')
      if (device.value?.mesh) for (const n of device.value.mesh) nodes.add(n.name || n.id)
      const exp = {}; for (const id of nodes) exp[id] = true
      expandedNodes.value = exp
    }
  } catch {}
  hostsLoading.value = false
}

function formatUptime (seconds) {
  if (!seconds) return '—'
  const d = Math.floor(seconds / 86400), h = Math.floor((seconds % 86400) / 3600), m = Math.floor((seconds % 3600) / 60)
  return d > 0 ? `${d}j ${h}h` : h > 0 ? `${h}h ${m}m` : `${m}m`
}

// Épuré : sections denses repliées par défaut (l'essentiel est dans le résumé en tête).
const showIdentity = ref(false)
const showTech = ref(false)
const showHosts = ref(false)
const clientCount = computed(() => (hosts.value && hosts.value.total != null) ? hosts.value.total : (device.value?.wifi?.totalClients ?? 0))
const SIG_LBL = { excellent: 'excellent', good: 'bon', fair: 'faible', bad: 'critique' }
const signalLabel = computed(() => SIG_LBL[signalQuality(props.doc.serial_number)] || '')
const signalBadgeColor = computed(() => { const s = signalQuality(props.doc.serial_number); return (s === 'excellent' || s === 'good') ? 'green' : s === 'fair' ? 'orange' : s === 'bad' ? 'red' : 'grey' })
const oltLabel = computed(() => props.doc.olt_name || props.doc.olt_ip || 'OLT')
// Événements récents (register / dying-gasp / online-offline) — depuis nos device_events (le poller les journalise déjà).
const events = ref([])
async function loadEvents () {
  if (!props.doc.serial_number) return
  try { const r = await fetch(`${HUB_URL}/devices/events?serial=${encodeURIComponent(props.doc.serial_number)}&limit=8`); if (r.ok) events.value = await r.json() } catch (e) { /* */ }
}
const lastOutage = computed(() => { const o = events.value.find(e => e.event === 'offline'); return o ? (o.reason || 'coupure') : null })
function evIcon (e) { return e.event === 'offline' ? 'power_off' : e.event === 'online' ? 'check_circle' : 'fiber_manual_record' }
function evColor (e) { return e.event === 'offline' ? 'orange-7' : e.event === 'online' ? 'green-6' : 'grey-5' }
function evLabel (e) { return e.event === 'offline' ? ('Hors ligne' + (e.reason ? ' · ' + e.reason : '')) : e.event === 'online' ? 'En ligne' : (e.event || '') }
function evTime (e) { try { return new Date(e.created_at).toLocaleString('fr-CA', { dateStyle: 'short', timeStyle: 'short' }) } catch (x) { return '' } }
// Graphe : disponibilité + signal Rx récents, depuis l'historique device_cache (vraie data ; le débit Mbps façon F = source ES, plus tard).
const wanHistory = ref([])
async function loadWanHistory () {
  if (!props.doc.serial_number) return
  try { const r = await fetch(`${HUB_URL}/devices/cache?serial=${encodeURIComponent(props.doc.serial_number)}`); if (r.ok) { const d = await r.json(); wanHistory.value = (Array.isArray(d.history) ? d.history : []).slice(-48) } } catch (e) { /* */ }
}
const uptimePct = computed(() => { const h = wanHistory.value; if (!h.length) return null; return Math.round(h.filter(p => p.online).length / h.length * 100) })
const lastRx = computed(() => { for (let i = wanHistory.value.length - 1; i >= 0; i--) { if (wanHistory.value[i].rxPower != null) return wanHistory.value[i].rxPower + ' dBm' } return '—' })
function fmtTs (ts) { try { return new Date(ts).toLocaleString('fr-CA', { dateStyle: 'short', timeStyle: 'short' }) } catch (e) { return '' } }

// Débit WAN EN DIRECT : mirroir de raisecom_rcmg_bandwith.php de F. Le hub lit les compteurs
// d'octets Counter64 de l'OLT (Raisecom/tech-2) ; ici on échantillonne toutes les 5 s et on
// calcule le débit = (Δoctets / Δs) × 8 (façon F). À la demande seulement (charge OLT).
const wanLive = ref(false)
const wanLiveErr = ref('')        // '' | 'unsupported' | 'error' | autre raison
const wanRates = ref([])          // [{ dl, ul, ts }] bits/s, ~40 derniers points
let wanTimer = null
let wanPrev = null                // dernier échantillon brut pour le delta
const WAN_ERR_MSG = {
  unsupported_olt_type: 'Débit en direct indisponible — OLT TP-Link (tech 3). Seules les OLT Raisecom exposent ce compteur.',
  onu_not_resolved: 'ONU absent du sondage OLT en ce moment (OLT hors supervision ?). Débit en direct indisponible.',
  no_counter: 'Aucun compteur de débit pour cet ONU sur l’OLT.',
  snmp_unavailable: 'Sonde SNMP indisponible.',
}
const wanErrMsg = computed(() => WAN_ERR_MSG[wanLiveErr.value] || (wanLiveErr.value ? 'Mesure indisponible pour le moment.' : ''))
const fmtBps = (bps) => (bps == null) ? '—' : (bps >= 1e6 ? (bps / 1e6).toFixed(1) + ' Mbps' : (bps / 1e3).toFixed(0) + ' kbps')
const wanNow = computed(() => wanRates.value.length ? wanRates.value[wanRates.value.length - 1] : null)
const wanMax = computed(() => { let m = 1; for (const r of wanRates.value) { if (r.dl > m) m = r.dl; if (r.ul > m) m = r.ul } return m })
function wanLine (key) {
  const r = wanRates.value; if (r.length < 2) return ''
  const max = wanMax.value, W = 220, H = 38, n = r.length
  return r.map((p, i) => `${((i / (n - 1)) * W).toFixed(1)},${(H - ((p[key] || 0) / max) * H).toFixed(1)}`).join(' ')
}
async function wanPoll () {
  if (!props.doc.serial_number) return
  try {
    const r = await fetch(`${HUB_URL}/collab/wan-rate?serial=${encodeURIComponent(props.doc.serial_number)}`)
    if (!r.ok) { wanLiveErr.value = 'error'; stopWanTimer(); return }
    const d = await r.json()
    // Indisponible (OLT non Raisecom, ONU non résolu, pas de compteur) = état non transitoire → on affiche le motif et on ARRÊTE de sonder (pas de spinner infini).
    if (!d.available) { wanLiveErr.value = d.reason || 'error'; stopWanTimer(); return }
    wanLiveErr.value = ''
    const cur = { ts: d.ts, rx: d.rxOctets, tx: d.txOctets }
    if (wanPrev) {
      const sec = (cur.ts - wanPrev.ts) / 1000
      const delta = (a, b) => { if (a == null || b == null) return null; let x = b - a; if (x < 0) x = 0; return sec > 0 ? (x / sec) * 8 : null }
      wanRates.value = [...wanRates.value, { dl: delta(wanPrev.rx, cur.rx), ul: delta(wanPrev.tx, cur.tx), ts: cur.ts }].slice(-40)
    }
    wanPrev = cur
  } catch (e) { wanLiveErr.value = 'error'; stopWanTimer() }
}
function startWanLive () { wanLive.value = true; wanLiveErr.value = ''; wanRates.value = []; wanPrev = null; wanPoll(); wanTimer = setInterval(wanPoll, 3000) }
function stopWanTimer () { if (wanTimer) { clearInterval(wanTimer); wanTimer = null } }
function stopWanLive () { stopWanTimer(); wanLive.value = false; wanLiveErr.value = '' }
function toggleWanLive () { wanLive.value ? stopWanLive() : startWanLive() }
// Liste d'appareils au clic sur l'icône : on déplie → la version EN CACHE s'affiche vite (le hub sert le cache par défaut) + anneau de chargement pendant le rafraîchissement.
function openHosts () { showHosts.value = true; if (!hosts.value && device.value) loadHosts() }

// Une action ONU a modifié le réseau → refléter le nouveau statut (optimiste) + re-sonder l'appareil.
function onOnuChanged ({ action } = {}) {
  const map = { suspend: 'Suspendu', unsuspend: 'Actif', activate: 'Actif', remove: 'Retiré' }
  if (map[action]) props.doc.status = map[action]
  setTimeout(doRefresh, 1500)
}

async function doRefresh () {
  if (!props.doc.serial_number) return
  refreshing.value = true
  try {
    await refreshDeviceParams(props.doc.serial_number)
    fetchOltStatus(props.doc.serial_number)
    setTimeout(() => { fetchStatus([{ serial_number: props.doc.serial_number }]); refreshing.value = false }, 3000)
  } catch { refreshing.value = false }
}

onMounted(async () => {
  if (props.doc.serial_number) {
    fetchStatus([{ serial_number: props.doc.serial_number }])
    fetchOltStatus(props.doc.serial_number)
    fetchPortContext(props.doc.serial_number).then(ctx => { portCtx.value = ctx })
    loadEvents()
    loadWanHistory()
  }
})

// Chargement PARESSEUX : on n'interroge le modem (modem-bridge) que si l'agent déplie « Appareils connectés ».
watch(showHosts, v => { if (v && !hosts.value && device.value) loadHosts() })
watch(() => props.doc.serial_number, sn => {
  stopWanLive() // arrêter la mesure live de l'appareil précédent
  if (sn) {
    hosts.value = null
    fetchStatus([{ serial_number: sn }])
    fetchOltStatus(sn)
    loadEvents(); loadWanHistory() // rafraîchir la bande dispo/événements pour le nouvel appareil
  }
})
onUnmounted(() => stopWanLive())
</script>

<style scoped>
.acs-badge-pulse { animation: badge-pulse 1.2s ease-in-out infinite; }
@keyframes badge-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}
.topo-summary { border: 1px solid #e2e8f0; border-radius: 10px; padding: 12px; background: #f8fafc; margin-bottom: 10px; }
.topo-row { display: flex; align-items: flex-start; justify-content: center; gap: 0; }
.topo-node { display: flex; flex-direction: column; align-items: center; width: 90px; }
.topo-modem { position: relative; }
.topo-dot { position: absolute; right: -2px; bottom: 2px; width: 9px; height: 9px; border-radius: 999px; border: 2px solid #f8fafc; }
.topo-dot.on { background: #21BA45; } .topo-dot.off { background: #C10015; }
.topo-cap { font-size: 0.72rem; color: #64748b; margin-top: 3px; text-align: center; }
.topo-link { flex: 1; max-width: 56px; height: 2px; background: #cbd5e1; margin-top: 12px; }
.topo-link.ok { background: #21BA45; opacity: .55; }
.topo-badges { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.topo-sub { font-size: 0.66rem; color: #94a3b8; }
.dv-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin-top: 10px; }
.dv-tile { border: 1px solid #e2e8f0; border-radius: 7px; padding: 6px 8px; background: #fff; }
.dv-tl { font-size: 0.68rem; color: #64748b; display: flex; align-items: center; gap: 3px; }
.dv-tl.dv-warn { color: #b45309; }
.dv-tv { font-size: 0.84rem; font-weight: 500; color: #1e293b; margin-top: 2px; }
.dv-link { color: #4f46e5; text-decoration: none; }
.dv-row2 { display: flex; gap: 8px; margin-top: 8px; }
.dv-wan, .dv-events { flex: 1; border: 1px solid #e2e8f0; border-radius: 8px; padding: 7px 9px; background: #fff; }
.dv-wan-ph { font-size: 0.78rem; color: #94a3b8; padding: 10px 0; text-align: center; }
.dv-ev { font-size: 0.74rem; color: #334155; padding: 2px 0; }
.topo-click { cursor: pointer; border-radius: 8px; transition: background .12s; }
.topo-click:hover { background: #eef2ff; }
.wan-strip { display: flex; gap: 2px; align-items: flex-end; height: 22px; margin-top: 4px; }
.wan-tick { flex: 1; min-width: 2px; height: 100%; border-radius: 1px; opacity: .85; }
.dv-wan-btn { float: right; margin-top: -2px; }
.wan-spark { width: 100%; height: 38px; display: block; margin-top: 4px; }
.collapse-head { display: flex; align-items: center; cursor: pointer; font-size: 0.8rem; font-weight: 600; color: #475569; padding: 6px 2px; border-top: 1px solid #f1f5f9; user-select: none; }
.collapse-head:hover { color: #1e293b; }
.diag-section { margin-top: 8px; }
.fibre-top { display: flex; align-items: center; font-size: 0.84rem; padding: 6px 8px; background: #f8fafc; border-radius: 6px; border: 1px solid #e2e8f0; }
.diag-section-title { font-size: 0.78rem; font-weight: 600; color: #4b5563; margin-bottom: 4px; display: flex; align-items: center; }
.diag-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 16px; }
.diag-item { display: flex; align-items: baseline; gap: 6px; padding: 4px 0; font-size: 0.84rem; border-bottom: 1px solid #f1f5f9; }
.diag-label { font-size: 0.72rem; font-weight: 600; color: #9ca3af; min-width: 72px; flex-shrink: 0; }
.ip-grid { display: flex; flex-direction: column; gap: 3px; }
.ip-item { display: flex; align-items: center; gap: 8px; padding: 3px 0; font-size: 0.84rem; }
.ip-role { font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.3px; padding: 1px 6px; border-radius: 3px; min-width: 58px; text-align: center; }
.ip-role--internet { background: #dcfce7; color: #166534; }
.ip-role--management { background: #dbeafe; color: #1e40af; }
.ip-role--service { background: #f3e8ff; color: #6b21a8; }
.ip-role--lan { background: #f1f5f9; color: #475569; }
.ip-addr { font-size: 0.84rem; font-weight: 600; }
.ip-link { color: #1e40af; text-decoration: none; font-family: monospace; }
.ip-link:hover { text-decoration: underline; }
.mesh-nodes { display: flex; flex-direction: column; gap: 6px; }
.mesh-node { padding: 6px 8px; background: #f8fafc; border-radius: 6px; border: 1px solid #e2e8f0; }
.mesh-node-header { display: flex; align-items: center; font-size: 0.84rem; }
.node-group { margin-bottom: 8px; border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; }
.node-group-header { display: flex; align-items: center; padding: 6px 10px; background: #f8fafc; border-bottom: 1px solid #e2e8f0; font-size: 0.82rem; cursor: pointer; user-select: none; }
.node-group-header:hover { background: #f1f5f9; }
.node-chevron { color: #9ca3af; margin-right: 2px; transition: transform 0.15s; }
.hosts-table { width: 100%; border-collapse: collapse; font-size: 0.8rem; }
.hosts-table th { text-align: left; font-size: 0.68rem; font-weight: 600; color: #9ca3af; padding: 3px 6px; border-bottom: 1px solid #f1f5f9; }
.hosts-table td { padding: 3px 6px; border-bottom: 1px solid #f1f5f9; }
.hosts-table code { font-size: 0.72rem; }
.port-ctx-bar { height: 6px; border-radius: 3px; background: #f1f5f9; display: flex; overflow: hidden; }
.port-ctx-fill--online { background: #4ade80; }
.port-ctx-fill--offline { background: #f87171; }
.port-ctx-legend { display: flex; align-items: center; }
.port-ctx-neighbors { display: flex; flex-direction: column; gap: 2px; }
.port-ctx-neighbor { padding: 2px 6px; background: #fef2f2; border-radius: 4px; border: 1px solid #fecaca; }
/* Advanced diagnostic */
.adv-issues { display: flex; flex-direction: column; gap: 6px; }
.adv-issue { padding: 8px 10px; border-radius: 6px; font-size: 0.82rem; }
.adv-issue--critical { background: #fef2f2; border: 1px solid #fecaca; }
.adv-issue--warning { background: #fffbeb; border: 1px solid #fde68a; }
.adv-issue--info { background: #eff6ff; border: 1px solid #bfdbfe; }
.adv-issue-header { display: flex; align-items: center; gap: 6px; }
.adv-issue--critical .adv-issue-header { color: #991b1b; }
.adv-issue--warning .adv-issue-header { color: #92400e; }
.adv-issue--info .adv-issue-header { color: #1e40af; }
.adv-issue-detail { font-size: 0.76rem; color: #6b7280; margin-top: 2px; padding-left: 22px; }
.adv-issue-action { font-size: 0.76rem; color: #4b5563; margin-top: 3px; padding-left: 22px; font-style: italic; }
.adv-ok { padding: 8px 10px; background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 6px; font-size: 0.82rem; color: #166534; display: flex; align-items: center; }
.adv-mesh-nodes { display: flex; flex-direction: column; gap: 6px; }
.adv-mesh-node { padding: 8px 10px; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; }
.adv-mesh-header { display: flex; align-items: center; font-size: 0.84rem; }
.adv-mesh-stats { display: flex; flex-wrap: wrap; gap: 8px 16px; margin-top: 4px; font-size: 0.78rem; }
.adv-mesh-stat { display: flex; align-items: baseline; gap: 4px; }
.adv-signal-bar { width: 40px; height: 6px; background: #e5e7eb; border-radius: 3px; display: inline-block; vertical-align: middle; margin-right: 4px; overflow: hidden; }
.adv-signal-fill { height: 100%; border-radius: 3px; transition: width 0.3s; }
.adv-clients-table td:first-child { white-space: nowrap; }
.adv-inactive-row { opacity: 0.5; font-style: italic; }

/* Mobile (agents terrain) : la carte réseau et les grilles doivent tenir dans
   l'écran plutôt que déborder/rogner (nœuds topo à largeur fixe = cause du clip). */
@media (max-width: 600px) {
  .topo-summary { padding: 10px; }
  .topo-row { gap: 2px; }
  .topo-node { width: auto; flex: 1 1 0; min-width: 0; }
  .topo-cap, .topo-sub { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .topo-link { max-width: 18px; }
  .dv-grid { grid-template-columns: repeat(2, 1fr); }
  .dv-row2 { flex-direction: column; }
  .diag-grid { grid-template-columns: 1fr; }
}
</style>
