<script lang="ts">
  import { onMount, onDestroy } from "svelte";

  interface BeforeInstallPromptEvent extends Event {
    prompt(): Promise<void>;
    readonly userChoice: Promise<{ outcome: "accepted" | "dismissed" }>;
  }
  import {
    fetchContainers,
    fetchImages,
    fetchVolumes,
    fetchNetworks,
    containerAction,
    connectEvents,
    getAutoUpdatePolicy,
    setAutoUpdatePolicy,
    updateNow,
    deleteImage,
    deleteVolume,
    deleteNetwork,
    type ContainerEntry,
    type ContainerPort,
    type ImageEntry,
    type VolumeEntry,
    type NetworkEntry,
    type AutoUpdatePolicy,
  } from "$lib/api";
  import LogModal from "$lib/LogModal.svelte";

  // ── Logs modal ────────────────────────────────────────────────────────────
  type LogTarget =
    | { mode: 'single'; agentId: string; containerId: string; name: string }
    | { mode: 'group'; agentId: string; projectName: string; containers: Array<{id: string; name: string}> };
  let logTarget = $state<LogTarget | null>(null);

  function openLogs(agentId: string, containerId: string, name: string) {
    logTarget = { mode: 'single', agentId, containerId, name };
  }

  function openProjectLogs(agentId: string, projectName: string, containers: ContainerEntry[]) {
    logTarget = {
      mode: 'group',
      agentId,
      projectName,
      containers: containers.map(e => ({ id: e.container.id, name: e.container.name })),
    };
  }

  // ── Tab state ─────────────────────────────────────────────────────────────
  type Tab = "containers" | "images" | "volumes" | "networks";
  let activeTab = $state<Tab>("containers");

  // ── Data ──────────────────────────────────────────────────────────────────
  let entries    = $state<ContainerEntry[] | null>(null);
  let images     = $state<ImageEntry[]    | null>(null);
  let volumes    = $state<VolumeEntry[]   | null>(null);
  let networks   = $state<NetworkEntry[]  | null>(null);
  let loadError  = $state<string | null>(null);
  let actionPending = $state<string | null>(null);
  let toast      = $state<{ msg: string; ok: boolean } | null>(null);

  // ── Collapsed states (independent per view) ───────────────────────────────
  let collapsed        = $state<Record<string, boolean>>({});
  let collapsedImages  = $state<Record<string, boolean>>({});
  let collapsedVolumes = $state<Record<string, boolean>>({});
  let collapsedNetworks= $state<Record<string, boolean>>({});
  let collapsedProjects = $state<Record<string, boolean>>({});
  let projectActionPending = $state<string | null>(null); // `${agentId}/${projectName}`
  let openKebab = $state<string | null>(null);

  // ── Orphan / delete state ─────────────────────────────────────────────────
  let deletePending = $state<string | null>(null);
  let showOrphansOnlyImages   = $state(false);
  let showOrphansOnlyVolumes  = $state(false);
  let showOrphansOnlyNetworks = $state(false);

  async function doDeleteImage(agentId: string, imageId: string) {
    if (!confirm("Supprimer cette image ?")) return;
    deletePending = imageId;
    try {
      await deleteImage(agentId, imageId, true);
      showToast("Image supprimée", true);
      await loadImages();
    } catch (e: unknown) {
      showToast(e instanceof Error ? e.message : String(e), false);
    } finally {
      deletePending = null;
    }
  }

  async function doDeleteVolume(agentId: string, volumeName: string) {
    if (!confirm(`Supprimer le volume "${volumeName}" ?`)) return;
    deletePending = volumeName;
    try {
      await deleteVolume(agentId, volumeName);
      showToast("Volume supprimé", true);
      await loadVolumes();
    } catch (e: unknown) {
      showToast(e instanceof Error ? e.message : String(e), false);
    } finally {
      deletePending = null;
    }
  }

  async function doDeleteNetwork(agentId: string, networkId: string) {
    if (!confirm("Supprimer ce réseau ?")) return;
    deletePending = networkId;
    try {
      await deleteNetwork(agentId, networkId);
      showToast("Réseau supprimé", true);
      await loadNetworks();
    } catch (e: unknown) {
      showToast(e instanceof Error ? e.message : String(e), false);
    } finally {
      deletePending = null;
    }
  }

  async function pruneAllImages(agentId: string, agentImages: ImageEntry[]) {
    const orphans = agentImages.filter(i => i.is_orphan);
    if (orphans.length === 0) return;
    if (!confirm(`Supprimer ${orphans.length} image(s) orpheline(s) ?`)) return;
    for (const img of orphans) {
      deletePending = img.id;
      try {
        await deleteImage(agentId, img.id, true);
      } catch (e: unknown) {
        showToast(e instanceof Error ? e.message : String(e), false);
      }
    }
    deletePending = null;
    showToast("Images orphelines supprimées", true);
    await loadImages();
  }

  async function pruneAllVolumes(agentId: string, agentVolumes: VolumeEntry[]) {
    const orphans = agentVolumes.filter(v => v.is_orphan);
    if (orphans.length === 0) return;
    if (!confirm(`Supprimer ${orphans.length} volume(s) orphelin(s) ?`)) return;
    for (const vol of orphans) {
      deletePending = vol.name;
      try {
        await deleteVolume(agentId, vol.name);
      } catch (e: unknown) {
        showToast(e instanceof Error ? e.message : String(e), false);
      }
    }
    deletePending = null;
    showToast("Volumes orphelins supprimés", true);
    await loadVolumes();
  }

  async function pruneAllNetworks(agentId: string, agentNetworks: NetworkEntry[]) {
    const orphans = agentNetworks.filter(n => n.is_orphan);
    if (orphans.length === 0) return;
    if (!confirm(`Supprimer ${orphans.length} réseau(x) orphelin(s) ?`)) return;
    for (const net of orphans) {
      deletePending = net.id;
      try {
        await deleteNetwork(agentId, net.id);
      } catch (e: unknown) {
        showToast(e instanceof Error ? e.message : String(e), false);
      }
    }
    deletePending = null;
    showToast("Réseaux orphelins supprimés", true);
    await loadNetworks();
  }

  // Group auto-update
  interface GroupAutoUpdateState {
    agentId: string;
    projectName: string;
    containerIds: string[];
    policy: AutoUpdatePolicy | null;
    loading: boolean;
    saving: boolean;
  }
  let groupAutoUpdate = $state<GroupAutoUpdateState | null>(null);
  let groupAutoUpdatePanelPos = $state<{ top: number; right: number } | null>(null);

  // ── Derived: containers grouped by agent ─────────────────────────────────
  const byAgent = $derived(
    (entries ?? []).reduce<Record<string, ContainerEntry[]>>((acc, e) => {
      (acc[e.agent_id] ??= []).push(e);
      return acc;
    }, {})
  );

  const sortedAgents = $derived(
    Object.entries(byAgent).sort(([, a], [, b]) => {
      const labelA = (a[0]?.alias || a[0]?.hostname || "").toLowerCase();
      const labelB = (b[0]?.alias || b[0]?.hostname || "").toLowerCase();
      return labelA.localeCompare(labelB);
    })
  );

  // ── Derived: images grouped by agent ─────────────────────────────────────
  const byAgentImages = $derived(
    (images ?? []).reduce<Record<string, ImageEntry[]>>((acc, e) => {
      (acc[e.agent_id] ??= []).push(e);
      return acc;
    }, {})
  );

  const sortedAgentImages = $derived(
    Object.entries(byAgentImages).sort(([, a], [, b]) => {
      const labelA = (a[0]?.alias || a[0]?.hostname || "").toLowerCase();
      const labelB = (b[0]?.alias || b[0]?.hostname || "").toLowerCase();
      return labelA.localeCompare(labelB);
    })
  );

  // ── Derived: volumes grouped by agent ────────────────────────────────────
  const byAgentVolumes = $derived(
    (volumes ?? []).reduce<Record<string, VolumeEntry[]>>((acc, e) => {
      (acc[e.agent_id] ??= []).push(e);
      return acc;
    }, {})
  );

  const sortedAgentVolumes = $derived(
    Object.entries(byAgentVolumes).sort(([, a], [, b]) => {
      const labelA = (a[0]?.alias || a[0]?.hostname || "").toLowerCase();
      const labelB = (b[0]?.alias || b[0]?.hostname || "").toLowerCase();
      return labelA.localeCompare(labelB);
    })
  );

  // ── Derived: networks grouped by agent ───────────────────────────────────
  const byAgentNetworks = $derived(
    (networks ?? []).reduce<Record<string, NetworkEntry[]>>((acc, e) => {
      (acc[e.agent_id] ??= []).push(e);
      return acc;
    }, {})
  );

  const sortedAgentNetworks = $derived(
    Object.entries(byAgentNetworks).sort(([, a], [, b]) => {
      const labelA = (a[0]?.alias || a[0]?.hostname || "").toLowerCase();
      const labelB = (b[0]?.alias || b[0]?.hostname || "").toLowerCase();
      return labelA.localeCompare(labelB);
    })
  );

  // ── PWA install prompt ────────────────────────────────────────────────────
  let installPrompt = $state<BeforeInstallPromptEvent | null>(null);

  function onBeforeInstallPrompt(e: Event) {
    e.preventDefault();
    installPrompt = e as BeforeInstallPromptEvent;
  }

  function onAppInstalled() {
    installPrompt = null;
  }

  async function installPWA() {
    if (!installPrompt) return;
    installPrompt.prompt();
    await installPrompt.userChoice;
    installPrompt = null;
  }

  // ── WebSocket ─────────────────────────────────────────────────────────────
  let disconnect: (() => void) | null = null;

  // ── Load functions ────────────────────────────────────────────────────────
  async function load() {
    loadError = null;
    try {
      entries = await fetchContainers() ?? [];
      // Pré-chargement en arrière-plan des policies pour colorer les boutons auto-update
      const toLoad = entries;
      Promise.allSettled(
        toLoad.map(e => getAutoUpdatePolicy(e.agent_id, e.container.id).then(policy => ({ key: autoUpdateKey(e.agent_id, e.container.id), policy })))
      ).then(results => {
        const updates: Record<string, AutoUpdateState> = {};
        for (const r of results) {
          if (r.status === "fulfilled") {
            updates[r.value.key] = { policy: r.value.policy, loading: false, saving: false };
          }
        }
        autoUpdateStates = { ...autoUpdateStates, ...updates };
      });
    } catch (e: unknown) {
      loadError = e instanceof Error ? e.message : String(e);
      entries = [];
    }
  }

  async function loadImages() {
    try {
      images = await fetchImages() ?? [];
    } catch (e: unknown) {
      loadError = e instanceof Error ? e.message : String(e);
      images = [];
    }
  }

  async function loadVolumes() {
    try {
      volumes = await fetchVolumes() ?? [];
    } catch (e: unknown) {
      loadError = e instanceof Error ? e.message : String(e);
      volumes = [];
    }
  }

  async function loadNetworks() {
    try {
      networks = await fetchNetworks() ?? [];
    } catch (e: unknown) {
      loadError = e instanceof Error ? e.message : String(e);
      networks = [];
    }
  }

  async function loadActiveTab() {
    if (activeTab === "containers") await load();
    else if (activeTab === "images")  await loadImages();
    else if (activeTab === "volumes") await loadVolumes();
    else if (activeTab === "networks") await loadNetworks();
  }

  async function switchTab(tab: Tab) {
    activeTab = tab;
    loadError = null;
    await loadActiveTab();
  }

  // ── Container actions ─────────────────────────────────────────────────────
  async function doAction(
    agentId: string,
    containerId: string,
    action: "start" | "stop" | "restart" | "remove"
  ) {
    actionPending = containerId;
    try {
      await containerAction(agentId, containerId, action);
      showToast(`${action} envoyé`, true);
      setTimeout(load, 1500);
    } catch (e: unknown) {
      showToast(e instanceof Error ? e.message : String(e), false);
    } finally {
      actionPending = null;
    }
  }

  function showToast(msg: string, ok: boolean) {
    toast = { msg, ok };
    setTimeout(() => (toast = null), 3000);
  }

  // ── Auto-update panel ────────────────────────────────────────────────────
  interface AutoUpdateState {
    policy: AutoUpdatePolicy | null;
    loading: boolean;
    saving: boolean;
  }

  let autoUpdateOpen = $state<string | null>(null); // containerKey = `${agentId}/${containerId}`
  let updateNowPending = $state<string | null>(null); // containerKey en cours d'update
  let autoUpdateStates = $state<Record<string, AutoUpdateState>>({});
  let autoUpdateDebounce = $state<ReturnType<typeof setTimeout> | null>(null);
  let autoUpdatePanelPos = $state<{ top: number; right: number } | null>(null);

  const INTERVAL_OPTIONS = [
    { label: "1 heure",   value: 60 },
    { label: "6 heures",  value: 360 },
    { label: "12 heures", value: 720 },
    { label: "24 heures", value: 1440 },
    { label: "7 jours",   value: 10080 },
  ];

  function autoUpdateKey(agentId: string, containerId: string) {
    return `${agentId}/${containerId}`;
  }

  async function openAutoUpdate(agentId: string, containerId: string, panelPos?: { top: number; right: number }) {
    const key = autoUpdateKey(agentId, containerId);
    if (autoUpdateOpen === key) {
      autoUpdateOpen = null;
      autoUpdatePanelPos = null;
      return;
    }
    autoUpdateOpen = key;
    if (panelPos) autoUpdatePanelPos = panelPos;
    // Si la policy a déjà été pré-chargée, on ne la réinitialise pas
    const existing = autoUpdateStates[key];
    if (!existing?.policy) {
      autoUpdateStates = {
        ...autoUpdateStates,
        [key]: { policy: existing?.policy ?? null, loading: true, saving: false },
      };
      try {
        const policy = await getAutoUpdatePolicy(agentId, containerId);
        autoUpdateStates = {
          ...autoUpdateStates,
          [key]: { policy, loading: false, saving: false },
        };
      } catch {
        autoUpdateStates = {
          ...autoUpdateStates,
          [key]: { policy: null, loading: false, saving: false },
        };
      }
    }
  }

  function scheduleAutoUpdateSave(agentId: string, containerId: string) {
    const key = autoUpdateKey(agentId, containerId);
    if (autoUpdateDebounce) clearTimeout(autoUpdateDebounce);
    autoUpdateDebounce = setTimeout(async () => {
      const state = autoUpdateStates[key];
      if (!state?.policy) return;
      autoUpdateStates = {
        ...autoUpdateStates,
        [key]: { ...state, saving: true },
      };
      try {
        const updated = await setAutoUpdatePolicy(agentId, containerId, {
          enabled: state.policy.enabled,
          interval_minutes: state.policy.interval_minutes,
        });
        autoUpdateStates = {
          ...autoUpdateStates,
          [key]: { policy: updated, loading: false, saving: false },
        };
      } catch (e: unknown) {
        showToast(e instanceof Error ? e.message : String(e), false);
        autoUpdateStates = {
          ...autoUpdateStates,
          [key]: { ...state, saving: false },
        };
      }
    }, 300);
  }

  function toggleAutoUpdateEnabled(agentId: string, containerId: string) {
    const key = autoUpdateKey(agentId, containerId);
    const state = autoUpdateStates[key];
    if (!state?.policy) return;
    autoUpdateStates = {
      ...autoUpdateStates,
      [key]: { ...state, policy: { ...state.policy, enabled: !state.policy.enabled } },
    };
    scheduleAutoUpdateSave(agentId, containerId);
  }

  function changeAutoUpdateInterval(agentId: string, containerId: string, minutes: number) {
    const key = autoUpdateKey(agentId, containerId);
    const state = autoUpdateStates[key];
    if (!state?.policy) return;
    autoUpdateStates = {
      ...autoUpdateStates,
      [key]: { ...state, policy: { ...state.policy, interval_minutes: minutes } },
    };
    scheduleAutoUpdateSave(agentId, containerId);
  }

  async function doUpdateNow(agentId: string, containerId: string) {
    const key = autoUpdateKey(agentId, containerId);
    updateNowPending = key;
    try {
      await updateNow(agentId, containerId);
      showToast("Mise à jour lancée", true);
      // Refresh panel après un délai pour montrer last_checked_at mis à jour
      if (autoUpdateOpen !== null) {
        const currentKey = autoUpdateOpen;
        const parts = currentKey.split('/');
        const panelAgentId = parts[0];
        const panelContainerId = parts.slice(1).join('/');
        setTimeout(async () => {
          if (autoUpdateOpen !== currentKey) return; // panel fermé entretemps
          try {
            const policy = await getAutoUpdatePolicy(panelAgentId, panelContainerId);
            autoUpdateStates = {
              ...autoUpdateStates,
              [currentKey]: { policy, loading: false, saving: false },
            };
          } catch {}
        }, 3000);
      }
    } catch (e: unknown) {
      showToast(e instanceof Error ? e.message : String(e), false);
    } finally {
      updateNowPending = null;
    }
  }

  function closeAutoUpdateOnClickOutside(e: MouseEvent) {
    if (autoUpdateOpen === null && groupAutoUpdate === null) return;
    const target = e.target as HTMLElement;
    if (!target.closest("[data-autoupdate-panel]") && !target.closest("[data-autoupdate-btn]")) {
      autoUpdateOpen = null;
      autoUpdatePanelPos = null;
    }
    if (!target.closest('[data-group-autoupdate-panel]') && !target.closest('[data-group-autoupdate-btn]')) {
      groupAutoUpdate = null;
      groupAutoUpdatePanelPos = null;
    }
  }

  function formatRelativeTime(iso: string | null): string {
    if (!iso) return "Jamais";
    const diff = Date.now() - new Date(iso).getTime();
    const s = Math.floor(diff / 1000);
    if (s < 60) return "Il y a quelques secondes";
    const m = Math.floor(s / 60);
    if (m < 60) return `Il y a ${m} min`;
    const h = Math.floor(m / 60);
    if (h < 24) return `Il y a ${h}h`;
    const d = Math.floor(h / 24);
    return `Il y a ${d}j`;
  }

  // ── Toggle helpers ────────────────────────────────────────────────────────
  function toggleSection(agentId: string)  { collapsed         = { ...collapsed,         [agentId]: !(collapsed[agentId]         ?? true) }; }
  function toggleImages(agentId: string)   { collapsedImages   = { ...collapsedImages,   [agentId]: !(collapsedImages[agentId]   ?? true) }; }
  function toggleVolumes(agentId: string)  { collapsedVolumes  = { ...collapsedVolumes,  [agentId]: !(collapsedVolumes[agentId]  ?? true) }; }
  function toggleNetworks(agentId: string) { collapsedNetworks = { ...collapsedNetworks, [agentId]: !(collapsedNetworks[agentId] ?? true) }; }

  // ── Project grouping ─────────────────────────────────────────────────────
  function groupContainersByProject(containers: ContainerEntry[]): {
    projects: [string, ContainerEntry[]][];
    standalone: ContainerEntry[];
  } {
    const map = new Map<string, ContainerEntry[]>();
    const standalone: ContainerEntry[] = [];
    for (const entry of containers) {
      const proj = entry.container.compose_project;
      if (proj) {
        if (!map.has(proj)) map.set(proj, []);
        map.get(proj)!.push(entry);
      } else {
        standalone.push(entry);
      }
    }
    return {
      projects: [...map.entries()].sort(([a], [b]) => a.localeCompare(b)),
      standalone: standalone.sort((a, b) => a.container.name.localeCompare(b.container.name)),
    };
  }

  function projectKey(agentId: string, projectName: string) {
    return `${agentId}/${projectName}`;
  }

  function toggleProject(agentId: string, projectName: string) {
    const k = projectKey(agentId, projectName);
    collapsedProjects = { ...collapsedProjects, [k]: collapsedProjects[k] === false };
  }

  // ── Project actions ───────────────────────────────────────────────────────
  async function doProjectAction(agentId: string, projectName: string, containers: ContainerEntry[], action: 'stop' | 'restart') {
    const key = projectKey(agentId, projectName);
    projectActionPending = key;
    try {
      await Promise.allSettled(
        containers.map(e => containerAction(agentId, e.container.id, action))
      );
      showToast(`${action} envoyé au projet ${projectName}`, true);
      setTimeout(load, 1500);
    } catch (e: unknown) {
      showToast(e instanceof Error ? e.message : String(e), false);
    } finally {
      projectActionPending = null;
    }
  }

  // ── Group auto-update ─────────────────────────────────────────────────────
  async function openGroupAutoUpdate(e: MouseEvent, agentId: string, projectName: string, containers: ContainerEntry[]) {
    const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
    if (groupAutoUpdate?.agentId === agentId && groupAutoUpdate?.projectName === projectName) {
      groupAutoUpdate = null;
      groupAutoUpdatePanelPos = null;
      return;
    }
    groupAutoUpdatePanelPos = { top: rect.bottom + 6, right: window.innerWidth - rect.right };
    groupAutoUpdate = {
      agentId,
      projectName,
      containerIds: containers.map(e => e.container.id),
      policy: null,
      loading: true,
      saving: false,
    };
    try {
      const policy = await getAutoUpdatePolicy(agentId, containers[0].container.id);
      if (groupAutoUpdate?.projectName === projectName) {
        groupAutoUpdate = { ...groupAutoUpdate, policy, loading: false };
      }
    } catch {
      if (groupAutoUpdate?.projectName === projectName) {
        groupAutoUpdate = { ...groupAutoUpdate, loading: false };
      }
    }
  }

  function toggleGroupAutoUpdateEnabled() {
    if (!groupAutoUpdate?.policy) return;
    groupAutoUpdate = { ...groupAutoUpdate, policy: { ...groupAutoUpdate.policy, enabled: !groupAutoUpdate.policy.enabled } };
    saveGroupAutoUpdate();
  }

  function changeGroupAutoUpdateInterval(minutes: number) {
    if (!groupAutoUpdate?.policy) return;
    groupAutoUpdate = { ...groupAutoUpdate, policy: { ...groupAutoUpdate.policy, interval_minutes: minutes } };
    saveGroupAutoUpdate();
  }

  let groupAutoUpdateDebounce: ReturnType<typeof setTimeout> | null = null;
  function saveGroupAutoUpdate() {
    if (groupAutoUpdateDebounce) clearTimeout(groupAutoUpdateDebounce);
    groupAutoUpdateDebounce = setTimeout(async () => {
      if (!groupAutoUpdate?.policy) return;
      const { agentId, containerIds, policy } = groupAutoUpdate;
      groupAutoUpdate = { ...groupAutoUpdate, saving: true };
      try {
        await Promise.allSettled(
          containerIds.map(cid => setAutoUpdatePolicy(agentId, cid, { enabled: policy.enabled, interval_minutes: policy.interval_minutes }))
        );
        // Sync individual states
        const updates: Record<string, { policy: AutoUpdatePolicy; loading: boolean; saving: boolean }> = {};
        for (const cid of containerIds) {
          updates[autoUpdateKey(agentId, cid)] = { policy, loading: false, saving: false };
        }
        autoUpdateStates = { ...autoUpdateStates, ...updates };
      } catch (e: unknown) {
        showToast(e instanceof Error ? e.message : String(e), false);
      } finally {
        if (groupAutoUpdate) groupAutoUpdate = { ...groupAutoUpdate, saving: false };
      }
    }, 300);
  }

  // ── Lifecycle ─────────────────────────────────────────────────────────────
  onMount(() => {
    load();
    disconnect = connectEvents((evt) => {
      if (evt.type === "containers.updated" || evt.type === "agent.connected" || evt.type === "agent.disconnected") {
        if (activeTab === "containers") load();
        // Refresh open auto-update panel
        if (autoUpdateOpen !== null) {
          const parts = autoUpdateOpen.split('/');
          const panelAgentId = parts[0];
          const panelContainerId = parts.slice(1).join('/');
          getAutoUpdatePolicy(panelAgentId, panelContainerId).then(policy => {
            autoUpdateStates = {
              ...autoUpdateStates,
              [autoUpdateOpen!]: { policy, loading: false, saving: false },
            };
          }).catch(() => {});
        }
      }
      if (evt.type === "resources.updated") {
        if (activeTab === "images")   loadImages();
        if (activeTab === "volumes")  loadVolumes();
        if (activeTab === "networks") loadNetworks();
      }
    });
    if ((window as any).__installPrompt) {
      installPrompt = (window as any).__installPrompt;
      (window as any).__installPrompt = null;
    }
    window.addEventListener("beforeinstallprompt", onBeforeInstallPrompt);
    window.addEventListener("appinstalled", onAppInstalled);
  });

  onDestroy(() => {
    disconnect?.();
    window.removeEventListener("beforeinstallprompt", onBeforeInstallPrompt);
    window.removeEventListener("appinstalled", onAppInstalled);
  });

  // ── Helpers ───────────────────────────────────────────────────────────────
  function uniquePorts(ports: ContainerPort[] | null) {
    const seen = new Set<string>();
    return (ports ?? []).filter(p => {
      if (p.host_port <= 0) return false;
      const key = `${p.host_port}:${p.container_port}:${p.protocol}`;
      if (seen.has(key)) return false;
      seen.add(key);
      return true;
    }).sort((a, b) => a.host_port - b.host_port);
  }

  function stateDotClass(state: string) {
    if (state === "running") return "dot-running";
    if (state === "exited")  return "dot-exited";
    return "dot-other";
  }

  function stateTextClass(state: string) {
    if (state === "running") return "text-signal-green";
    if (state === "exited")  return "text-signal-red";
    return "text-signal-yellow";
  }

  function agentDotClass(containers: ContainerEntry[]): string {
    if (containers.length === 0) return "dot-offline";
    const running = containers.filter(c => c.container.state === "running").length;
    if (running === containers.length) return "dot-running";
    if (running === 0) return "dot-exited";
    return "dot-other";
  }

  function formatSize(bytes: number): string {
    if (bytes >= 1_073_741_824) return `${(bytes / 1_073_741_824).toFixed(1)} GB`;
    if (bytes >= 1_048_576)     return `${(bytes / 1_048_576).toFixed(1)} MB`;
    return `${(bytes / 1024).toFixed(0)} KB`;
  }

  function formatDate(ts: number): string {
    return new Date(ts * 1000).toLocaleDateString("fr-FR");
  }

  function shortId(id: string): string {
    // sha256:abc... → take 12 chars after the colon (or from start)
    const bare = id.startsWith("sha256:") ? id.slice(7) : id;
    return bare.slice(0, 12);
  }
</script>

<svelte:head>
  <title>Nexarr</title>
</svelte:head>

{#if logTarget}
  {#if logTarget.mode === 'single'}
    <LogModal agentId={logTarget.agentId} containerId={logTarget.containerId}
              containerName={logTarget.name} onClose={() => (logTarget = null)} />
  {:else}
    <LogModal agentId={logTarget.agentId} projectName={logTarget.projectName}
              containers={logTarget.containers} onClose={() => (logTarget = null)} />
  {/if}
{/if}

<!-- Toast -->
{#if toast}
  <div class="fixed top-4 right-4 z-50 flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm
              font-medium shadow-2xl border transition-all
              {toast.ok
                ? 'bg-abyss-800 border-signal-green/30 text-signal-green'
                : 'bg-abyss-800 border-signal-red/30 text-signal-red'}">
    <span class="w-1.5 h-1.5 rounded-full {toast.ok ? 'bg-signal-green' : 'bg-signal-red'}"></span>
    {toast.msg}
  </div>
{/if}

<div class="min-h-screen bg-abyss-900 bg-grid-faint bg-grid text-slate-200" role="presentation" onclick={closeAutoUpdateOnClickOutside}>

  <!-- Top bar: stats + actions contextuel -->
  <header class="glass sticky top-0 z-40 px-5 py-3 flex items-center gap-3">
    <div class="flex items-center gap-3 text-xs text-slate-500 tabular-nums">
      {#if activeTab === "containers" && entries !== null}
        <span>{entries.length} containers · {Object.keys(byAgent).length} hosts</span>
      {:else if activeTab === "images" && images !== null}
        <span>{images.length} images · {Object.keys(byAgentImages).length} hosts</span>
      {:else if activeTab === "volumes" && volumes !== null}
        <span>{volumes.length} volumes · {Object.keys(byAgentVolumes).length} hosts</span>
      {:else if activeTab === "networks" && networks !== null}
        <span>{networks.length} networks · {Object.keys(byAgentNetworks).length} hosts</span>
      {/if}
    </div>

    <div class="ml-auto flex items-center gap-1">
      {#if installPrompt}
        <button onclick={installPWA} class="nav-btn" title="Installer l'application">
          <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.75"
              d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M7 10l5 5m0 0l5-5m-5 5V4" />
          </svg>
        </button>
      {/if}

      <button onclick={loadActiveTab} class="nav-btn" title="Actualiser">
        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.75"
            d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
        </svg>
      </button>
    </div>
  </header>

  <!-- Tab bar -->
  <div class="border-b border-white/[0.06] bg-abyss-900/80 px-4 md:px-6">
    <nav class="flex max-w-7xl mx-auto -mb-px overflow-x-auto">
      {#each ([
        { id: "containers", label: "Containers" },
        { id: "images",     label: "Images" },
        { id: "volumes",    label: "Volumes" },
        { id: "networks",   label: "Networks" },
      ] as { id: Tab; label: string }[]) as tab}
        <button
          onclick={() => switchTab(tab.id)}
          class="px-3 sm:px-4 py-3 text-sm font-medium border-b-2 shrink-0 transition-colors
                 {activeTab === tab.id
                   ? 'border-cyan-400 text-cyan-400'
                   : 'border-transparent text-slate-400 hover:text-slate-200 hover:border-slate-600'}"
        >
          {tab.label}
        </button>
      {/each}
    </nav>
  </div>

  <main class="p-4 md:p-6 max-w-7xl mx-auto">

    <!-- ═══════════════════════════════════════════════════
         CONTAINERS TAB
    ════════════════════════════════════════════════════ -->
    {#if activeTab === "containers"}
      {#if entries === null}
        <div class="flex flex-col items-center justify-center h-64 gap-3 text-slate-600">
          <div class="w-8 h-8 border-2 border-emerald/30 border-t-emerald-bright rounded-full animate-spin"></div>
          <span class="text-sm">Chargement…</span>
        </div>

      {:else if loadError}
        <div class="flex items-center gap-3 max-w-md mx-auto mt-16 p-4 card border-signal-red/20">
          <span class="text-signal-red text-xl">⚠</span>
          <p class="text-signal-red text-sm">{loadError}</p>
        </div>

      {:else if Object.keys(byAgent).length === 0}
        <div class="flex flex-col items-center justify-center h-64 gap-2 text-slate-600">
          <svg class="w-10 h-10 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M5 12h14M12 5l7 7-7 7"/>
          </svg>
          <span class="text-sm">Aucun agent connecté</span>
        </div>

      {:else}
        {#each sortedAgents as [agentId, containers]}
          {#if containers.length > 0}
          {@const first = containers[0]}
          <section class="mb-8">

            <button
              class="flex items-center gap-2.5 mb-3 px-1 w-full text-left cursor-pointer group"
              onclick={() => toggleSection(agentId)}
              type="button"
            >
              <svg
                class="w-3.5 h-3.5 text-slate-500 transition-transform duration-200 shrink-0
                       {collapsed[agentId] !== false ? '-rotate-90' : 'rotate-0'}"
                fill="none" stroke="currentColor" viewBox="0 0 24 24"
              >
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" />
              </svg>
              <span class={agentDotClass(containers)}></span>
              <h2 class="font-semibold text-slate-200 text-sm group-hover:text-slate-100 transition-colors">
                {first.alias || first.hostname}
              </h2>
              {#if first.alias}
                <span class="hidden sm:inline font-mono text-xs text-slate-600">{first.hostname}</span>
              {/if}
              {#if first.ip_address}
                <span class="hidden sm:inline font-mono text-xs text-slate-500 bg-abyss-700 px-2 py-0.5 rounded-full
                             border border-white/[0.06]">{first.ip_address}</span>
              {/if}
              <span class="text-xs text-slate-600 ml-auto">
                {containers.length} container{containers.length !== 1 ? "s" : ""}
              </span>
            </button>

            {#if collapsed[agentId] === false}
            {@const grouped = groupContainersByProject(containers)}
            <div class="bg-abyss-800/40 rounded-xl border border-white/[0.04] px-4 py-3">

            <!-- Groupes de projets -->
            {#each grouped.projects as [projectName, projectContainers]}
              {@const projKey = projectKey(agentId, projectName)}
              {@const isProjectExpanded = collapsedProjects[projKey] === false}
              {@const isProjPending = projectActionPending === projKey}
              {@const isGroupAutoUpdateOpen = groupAutoUpdate?.agentId === agentId && groupAutoUpdate?.projectName === projectName}
              {@const anyAutoUpdateEnabled = projectContainers.some(e => autoUpdateStates[autoUpdateKey(agentId, e.container.id)]?.policy?.enabled)}

              <div class="pl-2 mb-3">
              <!-- En-tête projet -->
              <div class="flex items-center gap-2 px-1 mb-1">
                <button
                  class="flex items-center gap-2 flex-1 min-w-0 text-left cursor-pointer group/proj"
                  onclick={() => toggleProject(agentId, projectName)}
                  type="button"
                >
                  <svg class="w-3 h-3 text-slate-600 transition-transform duration-150 shrink-0
                               {isProjectExpanded ? 'rotate-0' : '-rotate-90'}"
                    fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" />
                  </svg>
                  <span class={agentDotClass(projectContainers)}></span>
                  <span class="text-xs font-semibold text-slate-300 group-hover/proj:text-slate-100 transition-colors font-mono">
                    {projectName}
                  </span>
                  <span class="hidden sm:inline text-xs text-slate-600">
                    {projectContainers.length} container{projectContainers.length !== 1 ? 's' : ''}
                  </span>
                </button>

                <!-- Boutons groupe -->
                <div class="flex items-center gap-1 shrink-0">
                  {@render ActionBtn({ label: "Logs", variant: "cyan", loading: false,
                    onclick: () => openProjectLogs(agentId, projectName, projectContainers) })}

                  <!-- Desktop : boutons complets -->
                  <div class="hidden sm:flex items-center gap-1">
                    {@render ActionBtn({ label: "Stop All", variant: "ghost", loading: isProjPending,
                      onclick: () => doProjectAction(agentId, projectName, projectContainers, 'stop') })}
                    {@render ActionBtn({ label: "Restart All", variant: "ghost", loading: isProjPending,
                      onclick: () => doProjectAction(agentId, projectName, projectContainers, 'restart') })}
                    <button
                      data-group-autoupdate-btn
                      onclick={(e) => { e.stopPropagation(); openGroupAutoUpdate(e, agentId, projectName, projectContainers); }}
                      title="Auto-update groupe"
                      class="px-2 py-1 rounded-lg text-xs font-medium transition-all border
                             {isGroupAutoUpdateOpen || anyAutoUpdateEnabled
                               ? 'bg-violet-500/20 text-violet-300 border-violet-500/40'
                               : 'bg-white/[0.05] hover:bg-white/[0.09] text-slate-500 hover:text-slate-300 border-white/[0.08]'}"
                    >
                      <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                          d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
                      </svg>
                    </button>
                  </div>

                  <!-- Mobile : kebab menu -->
                  <div class="relative sm:hidden">
                    <button
                      onclick={(e) => { e.stopPropagation(); openKebab = openKebab === projKey ? null : projKey; }}
                      class="px-2 py-1 rounded-lg text-xs font-medium transition-all border
                             bg-white/[0.05] hover:bg-white/[0.09] text-slate-500 hover:text-slate-300 border-white/[0.08]"
                      title="Plus d'actions"
                    >
                      <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M12 5v.01M12 12v.01M12 19v.01" />
                      </svg>
                    </button>

                    {#if openKebab === projKey}
                      <!-- Overlay pour fermer -->
                      <button
                        class="fixed inset-0 z-40"
                        onclick={() => openKebab = null}
                        aria-label="Fermer le menu"
                      ></button>
                      <!-- Dropdown -->
                      <div class="absolute right-0 top-full mt-1 z-50 min-w-[140px] bg-abyss-800 border border-white/[0.08] rounded-lg shadow-xl overflow-hidden">
                        <button
                          class="w-full text-left px-3 py-2 text-xs text-slate-300 hover:bg-white/[0.06] transition-colors"
                          onclick={(e) => { e.stopPropagation(); doProjectAction(agentId, projectName, projectContainers, 'stop'); openKebab = null; }}
                        >Stop All</button>
                        <button
                          class="w-full text-left px-3 py-2 text-xs text-slate-300 hover:bg-white/[0.06] transition-colors"
                          onclick={(e) => { e.stopPropagation(); doProjectAction(agentId, projectName, projectContainers, 'restart'); openKebab = null; }}
                        >Restart All</button>
                        <button
                          data-group-autoupdate-btn
                          class="w-full text-left px-3 py-2 text-xs transition-colors border-t border-white/[0.04]
                                 {anyAutoUpdateEnabled ? 'text-violet-300 hover:bg-violet-500/10' : 'text-slate-300 hover:bg-white/[0.06]'}"
                          onclick={(e) => { e.stopPropagation(); openKebab = null; openGroupAutoUpdate(e, agentId, projectName, projectContainers); }}
                        >Auto-update</button>
                      </div>
                    {/if}
                  </div>
                </div>
              </div>

              <!-- Desktop table du projet (collapsible) -->
              {#if isProjectExpanded}
              <div class="hidden md:block card overflow-hidden mt-1">
                <table class="w-full text-sm">
                  <thead>
                    <tr class="border-b border-white/[0.06] text-xs uppercase tracking-wider text-slate-600">
                      <th class="px-4 py-3 text-left font-medium">Nom</th>
                      <th class="px-4 py-3 text-left font-medium">Image</th>
                      <th class="px-4 py-3 text-left font-medium">État</th>
                      <th class="px-4 py-3 text-left font-medium">Ports</th>
                      <th class="px-4 py-3 text-right font-medium">Actions</th>
                    </tr>
                  </thead>
                  <tbody>
                    {#each projectContainers.slice().sort((a, b) => a.container.name.localeCompare(b.container.name)) as { agent_id, container } (container.id)}
                      <tr class="border-b border-white/[0.04] last:border-0 hover:bg-white/[0.025] transition-colors group">
                        <td class="px-4 py-3">
                          <div class="flex items-center gap-2.5">
                            <span class={stateDotClass(container.state)}></span>
                            <span class="font-mono text-slate-200 text-xs font-medium">{container.name}</span>
                          </div>
                        </td>
                        <td class="px-4 py-3 font-mono text-xs text-slate-500 max-w-[220px] truncate" title={container.image}>{container.image}</td>
                        <td class="px-4 py-3">
                          <span class="text-xs font-medium {stateTextClass(container.state)}">{container.state}</span>
                        </td>
                        <td class="px-4 py-3">
                          <div class="flex flex-wrap gap-1">
                            {#each uniquePorts(container.ports) as port}
                              <a
                                href="http://{byAgent[agent_id]?.[0]?.ip_address ?? ''}:{port.host_port}"
                                target="_blank"
                                rel="noopener noreferrer"
                                class="font-mono text-xs px-1.5 py-0.5 rounded bg-signal-cyan/10 text-signal-cyan border border-signal-cyan/20 hover:bg-signal-cyan/25 hover:border-signal-cyan/40 transition-colors cursor-pointer"
                              >
                                {port.host_port}:{port.container_port}
                              </a>
                            {/each}
                          </div>
                        </td>
                        <td class="px-4 py-3">
                          <div class="flex justify-end gap-1.5 relative">
                            {@render ActionBtn({ label: "Logs", variant: "cyan", loading: false,
                              onclick: () => openLogs(agent_id, container.id, container.name) })}
                            {#if container.state !== "running"}
                              {@render ActionBtn({ label: "Start", variant: "green", loading: actionPending === container.id,
                                onclick: () => doAction(agent_id, container.id, "start") })}
                            {:else}
                              {@render ActionBtn({ label: "Stop", variant: "ghost", loading: actionPending === container.id,
                                onclick: () => doAction(agent_id, container.id, "stop") })}
                              {@render ActionBtn({ label: "Restart", variant: "ghost", loading: actionPending === container.id,
                                onclick: () => doAction(agent_id, container.id, "restart") })}
                            {/if}
                            {@render AutoUpdateBtn(agent_id, container.id)}
                          </div>
                        </td>
                      </tr>
                    {/each}
                  </tbody>
                </table>
              </div>

              <!-- Mobile cards du projet (collapsible) -->
              <div class="md:hidden space-y-2 mt-1">
                {#each projectContainers.slice().sort((a, b) => a.container.name.localeCompare(b.container.name)) as { agent_id, container } (container.id)}
                  <div class="card p-4">
                    <div class="flex items-start justify-between gap-2 mb-2">
                      <div class="flex items-center gap-2 min-w-0">
                        <span class={stateDotClass(container.state)}></span>
                        <span class="font-mono text-sm font-medium truncate text-slate-200">{container.name}</span>
                      </div>
                      <span class="text-xs font-medium shrink-0 {stateTextClass(container.state)}">{container.state}</span>
                    </div>
                    <p class="font-mono text-xs text-slate-600 truncate mb-3">{container.image}</p>
                    {#if uniquePorts(container.ports).length > 0}
                      <div class="flex flex-wrap gap-1 mb-3">
                        {#each uniquePorts(container.ports) as port}
                          <a
                            href="http://{byAgent[agent_id]?.[0]?.ip_address ?? ''}:{port.host_port}"
                            target="_blank"
                            rel="noopener noreferrer"
                            class="font-mono text-xs px-1.5 py-0.5 rounded bg-signal-cyan/10 text-signal-cyan border border-signal-cyan/20 hover:bg-signal-cyan/25 hover:border-signal-cyan/40 transition-colors cursor-pointer"
                          >
                            {port.host_port}:{port.container_port}
                          </a>
                        {/each}
                      </div>
                    {/if}
                    <div class="flex gap-2 flex-wrap relative">
                      {@render ActionBtn({ label: "Logs", variant: "cyan", loading: false,
                        onclick: () => openLogs(agent_id, container.id, container.name) })}
                      {#if container.state !== "running"}
                        {@render ActionBtn({ label: "Start", variant: "green", loading: actionPending === container.id,
                          onclick: () => doAction(agent_id, container.id, "start") })}
                      {:else}
                        {@render ActionBtn({ label: "Stop", variant: "ghost", loading: actionPending === container.id,
                          onclick: () => doAction(agent_id, container.id, "stop") })}
                        {@render ActionBtn({ label: "Restart", variant: "ghost", loading: actionPending === container.id,
                          onclick: () => doAction(agent_id, container.id, "restart") })}
                      {/if}
                      {@render AutoUpdateBtn(agent_id, container.id)}
                    </div>
                  </div>
                {/each}
              </div>
              {/if}
              </div>
            {/each}

            <!-- Containers standalone -->
            {#if grouped.standalone.length > 0}
              <div class="pl-2 {grouped.projects.length > 0 ? 'mt-4' : ''}">
              {#if grouped.projects.length > 0}
                <div class="flex items-center gap-2 px-1 border-t border-white/[0.04] pt-3 mt-1 mb-1">
                  <span class="text-xs text-slate-600 uppercase tracking-wider">Standalone</span>
                </div>
              {/if}
              <!-- Desktop table standalone -->
              <div class="hidden md:block card overflow-hidden mt-1">
                <table class="w-full text-sm">
                  <thead>
                    <tr class="border-b border-white/[0.06] text-xs uppercase tracking-wider text-slate-600">
                      <th class="px-4 py-3 text-left font-medium">Nom</th>
                      <th class="px-4 py-3 text-left font-medium">Image</th>
                      <th class="px-4 py-3 text-left font-medium">État</th>
                      <th class="px-4 py-3 text-left font-medium">Ports</th>
                      <th class="px-4 py-3 text-right font-medium">Actions</th>
                    </tr>
                  </thead>
                  <tbody>
                    {#each grouped.standalone as { agent_id, container } (container.id)}
                      <tr class="border-b border-white/[0.04] last:border-0 hover:bg-white/[0.025] transition-colors group">
                        <td class="px-4 py-3">
                          <div class="flex items-center gap-2.5">
                            <span class={stateDotClass(container.state)}></span>
                            <span class="font-mono text-slate-200 text-xs font-medium">{container.name}</span>
                          </div>
                        </td>
                        <td class="px-4 py-3 font-mono text-xs text-slate-500 max-w-[220px] truncate" title={container.image}>{container.image}</td>
                        <td class="px-4 py-3">
                          <span class="text-xs font-medium {stateTextClass(container.state)}">{container.state}</span>
                        </td>
                        <td class="px-4 py-3">
                          <div class="flex flex-wrap gap-1">
                            {#each uniquePorts(container.ports) as port}
                              <a
                                href="http://{byAgent[agent_id]?.[0]?.ip_address ?? ''}:{port.host_port}"
                                target="_blank"
                                rel="noopener noreferrer"
                                class="font-mono text-xs px-1.5 py-0.5 rounded bg-signal-cyan/10 text-signal-cyan border border-signal-cyan/20 hover:bg-signal-cyan/25 hover:border-signal-cyan/40 transition-colors cursor-pointer"
                              >
                                {port.host_port}:{port.container_port}
                              </a>
                            {/each}
                          </div>
                        </td>
                        <td class="px-4 py-3">
                          <div class="flex justify-end gap-1.5 relative">
                            {@render ActionBtn({ label: "Logs", variant: "cyan", loading: false,
                              onclick: () => openLogs(agent_id, container.id, container.name) })}
                            {#if container.state !== "running"}
                              {@render ActionBtn({ label: "Start", variant: "green", loading: actionPending === container.id,
                                onclick: () => doAction(agent_id, container.id, "start") })}
                            {:else}
                              {@render ActionBtn({ label: "Stop", variant: "ghost", loading: actionPending === container.id,
                                onclick: () => doAction(agent_id, container.id, "stop") })}
                              {@render ActionBtn({ label: "Restart", variant: "ghost", loading: actionPending === container.id,
                                onclick: () => doAction(agent_id, container.id, "restart") })}
                            {/if}
                            {@render AutoUpdateBtn(agent_id, container.id)}
                          </div>
                        </td>
                      </tr>
                    {/each}
                  </tbody>
                </table>
              </div>

              <!-- Mobile cards standalone -->
              <div class="md:hidden space-y-2 mt-1">
                {#each grouped.standalone as { agent_id, container } (container.id)}
                  <div class="card p-4">
                    <div class="flex items-start justify-between gap-2 mb-2">
                      <div class="flex items-center gap-2 min-w-0">
                        <span class={stateDotClass(container.state)}></span>
                        <span class="font-mono text-sm font-medium truncate text-slate-200">{container.name}</span>
                      </div>
                      <span class="text-xs font-medium shrink-0 {stateTextClass(container.state)}">{container.state}</span>
                    </div>
                    <p class="font-mono text-xs text-slate-600 truncate mb-3">{container.image}</p>
                    {#if uniquePorts(container.ports).length > 0}
                      <div class="flex flex-wrap gap-1 mb-3">
                        {#each uniquePorts(container.ports) as port}
                          <a
                            href="http://{byAgent[agent_id]?.[0]?.ip_address ?? ''}:{port.host_port}"
                            target="_blank"
                            rel="noopener noreferrer"
                            class="font-mono text-xs px-1.5 py-0.5 rounded bg-signal-cyan/10 text-signal-cyan border border-signal-cyan/20 hover:bg-signal-cyan/25 hover:border-signal-cyan/40 transition-colors cursor-pointer"
                          >
                            {port.host_port}:{port.container_port}
                          </a>
                        {/each}
                      </div>
                    {/if}
                    <div class="flex gap-2 flex-wrap relative">
                      {@render ActionBtn({ label: "Logs", variant: "cyan", loading: false,
                        onclick: () => openLogs(agent_id, container.id, container.name) })}
                      {#if container.state !== "running"}
                        {@render ActionBtn({ label: "Start", variant: "green", loading: actionPending === container.id,
                          onclick: () => doAction(agent_id, container.id, "start") })}
                      {:else}
                        {@render ActionBtn({ label: "Stop", variant: "ghost", loading: actionPending === container.id,
                          onclick: () => doAction(agent_id, container.id, "stop") })}
                        {@render ActionBtn({ label: "Restart", variant: "ghost", loading: actionPending === container.id,
                          onclick: () => doAction(agent_id, container.id, "restart") })}
                      {/if}
                      {@render AutoUpdateBtn(agent_id, container.id)}
                    </div>
                  </div>
                {/each}
              </div>
              </div>
            {/if}

            </div>
            {/if}

          </section>
          {/if}
        {/each}
      {/if}

    <!-- ═══════════════════════════════════════════════════
         IMAGES TAB
    ════════════════════════════════════════════════════ -->
    {:else if activeTab === "images"}
      {#if images === null}
        <div class="flex flex-col items-center justify-center h-64 gap-3 text-slate-600">
          <div class="w-8 h-8 border-2 border-cyan-400/30 border-t-cyan-400 rounded-full animate-spin"></div>
          <span class="text-sm">Chargement…</span>
        </div>

      {:else if loadError}
        <div class="flex items-center gap-3 max-w-md mx-auto mt-16 p-4 card border-signal-red/20">
          <span class="text-signal-red text-xl">⚠</span>
          <p class="text-signal-red text-sm">{loadError}</p>
        </div>

      {:else if sortedAgentImages.length === 0}
        <div class="flex flex-col items-center justify-center h-64 gap-2 text-slate-600">
          <svg class="w-10 h-10 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
              d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
          </svg>
          <span class="text-sm">Aucune image disponible</span>
        </div>

      {:else}
        <!-- Orphans filter toggle -->
        <div class="flex items-center justify-end mb-4">
          <button
            onclick={() => showOrphansOnlyImages = !showOrphansOnlyImages}
            class="text-xs px-2.5 py-1 rounded-lg border transition-colors
                   {showOrphansOnlyImages
                     ? 'bg-red-500/15 text-red-400 border-red-500/30'
                     : 'bg-white/[0.05] text-slate-400 border-white/[0.08] hover:bg-white/[0.09]'}"
          >
            {showOrphansOnlyImages ? "Toutes les images" : "Orphelins seulement"}
          </button>
        </div>

        {#each sortedAgentImages as [agentId, agentImages]}
          {@const displayedImages = showOrphansOnlyImages ? agentImages.filter(i => i.is_orphan) : agentImages}
          {#if displayedImages.length > 0}
          {@const first = agentImages[0]}
          <section class="mb-8">

            <div class="flex items-center gap-2.5 mb-3 px-1">
              <button
                class="flex items-center gap-2.5 flex-1 min-w-0 text-left cursor-pointer group"
                onclick={() => toggleImages(agentId)}
                type="button"
              >
                <svg
                  class="w-3.5 h-3.5 text-slate-500 transition-transform duration-200 shrink-0
                         {collapsedImages[agentId] !== false ? '-rotate-90' : 'rotate-0'}"
                  fill="none" stroke="currentColor" viewBox="0 0 24 24"
                >
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" />
                </svg>
                <span class="dot-running"></span>
                <h2 class="font-semibold text-slate-200 text-sm group-hover:text-slate-100 transition-colors">
                  {first.alias || first.hostname}
                </h2>
                {#if first.alias}
                  <span class="hidden sm:inline font-mono text-xs text-slate-600">{first.hostname}</span>
                {/if}
                {#if first.ip_address}
                  <span class="hidden sm:inline font-mono text-xs text-slate-500 bg-abyss-700 px-2 py-0.5 rounded-full
                               border border-white/[0.06]">{first.ip_address}</span>
                {/if}
                <span class="text-xs text-slate-600 ml-auto">
                  {agentImages.length} image{agentImages.length !== 1 ? "s" : ""}
                </span>
              </button>
              {#if agentImages.some(i => i.is_orphan)}
                <button
                  onclick={() => pruneAllImages(agentId, agentImages)}
                  class="shrink-0 text-xs px-2 py-1 rounded border border-red-500/30 text-red-400 hover:bg-red-500/10 transition-colors"
                >
                  Prune orphans
                </button>
              {/if}
            </div>

            {#if collapsedImages[agentId] === false}
            <div class="card overflow-hidden">
              <!-- Desktop table -->
              <table class="hidden md:table w-full text-sm">
                <thead>
                  <tr class="border-b border-white/[0.06] text-xs uppercase tracking-wider text-slate-600">
                    <th class="px-4 py-3 text-left font-medium">Tags</th>
                    <th class="px-4 py-3 text-left font-medium">ID</th>
                    <th class="px-4 py-3 text-left font-medium">Taille</th>
                    <th class="px-4 py-3 text-left font-medium">Date</th>
                    <th class="px-4 py-3 text-right font-medium">Actions</th>
                  </tr>
                </thead>
                <tbody>
                  {#each displayedImages.slice().sort((a, b) => (a.tags[0] ?? a.id).localeCompare(b.tags[0] ?? b.id)) as img (img.id)}
                    <tr class="border-b border-white/[0.04] last:border-0 hover:bg-white/[0.025] transition-colors
                               {img.is_orphan ? 'bg-red-500/[0.03]' : ''}">
                      <td class="px-4 py-3">
                        <div class="flex flex-wrap items-center gap-1">
                          {#if img.tags?.length > 0}
                            {#each img.tags as tag}
                              <span class="font-mono text-xs px-1.5 py-0.5 rounded
                                           bg-cyan-400/10 text-cyan-400 border border-cyan-400/20">
                                {tag}
                              </span>
                            {/each}
                          {:else}
                            <span class="text-xs text-slate-600 italic">&lt;none&gt;</span>
                          {/if}
                          {#if img.is_orphan}
                            <span class="text-xs px-1.5 py-0.5 rounded bg-red-500/10 text-red-400 border border-red-500/20">orphan</span>
                          {/if}
                        </div>
                      </td>
                      <td class="px-4 py-3 font-mono text-xs text-slate-500">{shortId(img.id)}</td>
                      <td class="px-4 py-3 text-xs text-slate-400 tabular-nums">{formatSize(img.size)}</td>
                      <td class="px-4 py-3 text-xs text-slate-500">{formatDate(img.created_at)}</td>
                      <td class="px-4 py-3 text-right">
                        {#if img.is_orphan}
                          <button
                            onclick={() => doDeleteImage(agentId, img.id)}
                            disabled={deletePending === img.id}
                            class="text-xs px-2 py-1 rounded border border-red-500/30 text-red-400 hover:bg-red-500/10 transition-colors disabled:opacity-40"
                          >
                            {deletePending === img.id ? "…" : "Delete"}
                          </button>
                        {/if}
                      </td>
                    </tr>
                  {/each}
                </tbody>
              </table>

              <!-- Mobile cards -->
              <div class="md:hidden divide-y divide-white/[0.04]">
                {#each displayedImages.slice().sort((a, b) => (a.tags[0] ?? a.id).localeCompare(b.tags[0] ?? b.id)) as img (img.id)}
                  <div class="p-4 {img.is_orphan ? 'bg-red-500/[0.03]' : ''}">
                    <div class="flex items-start justify-between gap-2 mb-1">
                      <div class="flex flex-wrap items-center gap-1 min-w-0">
                        {#if img.tags?.length > 0}
                          {#each img.tags as tag}
                            <span class="font-mono text-xs px-1.5 py-0.5 rounded bg-cyan-400/10 text-cyan-400 border border-cyan-400/20">{tag}</span>
                          {/each}
                        {:else}
                          <span class="text-xs text-slate-600 italic">&lt;none&gt;</span>
                        {/if}
                        {#if img.is_orphan}
                          <span class="text-xs px-1.5 py-0.5 rounded bg-red-500/10 text-red-400 border border-red-500/20">orphan</span>
                        {/if}
                      </div>
                      {#if img.is_orphan}
                        <button
                          onclick={() => doDeleteImage(agentId, img.id)}
                          disabled={deletePending === img.id}
                          class="shrink-0 text-xs px-2 py-1 rounded border border-red-500/30 text-red-400 hover:bg-red-500/10 transition-colors disabled:opacity-40"
                        >
                          {deletePending === img.id ? "…" : "Delete"}
                        </button>
                      {/if}
                    </div>
                    <div class="flex items-center gap-3 text-xs text-slate-500 mt-1">
                      <span class="font-mono">{shortId(img.id)}</span>
                      <span class="tabular-nums text-slate-400">{formatSize(img.size)}</span>
                      <span>{formatDate(img.created_at)}</span>
                    </div>
                  </div>
                {/each}
              </div>
            </div>
            {/if}

          </section>
          {/if}
        {/each}
      {/if}

    <!-- ═══════════════════════════════════════════════════
         VOLUMES TAB
    ════════════════════════════════════════════════════ -->
    {:else if activeTab === "volumes"}
      {#if volumes === null}
        <div class="flex flex-col items-center justify-center h-64 gap-3 text-slate-600">
          <div class="w-8 h-8 border-2 border-cyan-400/30 border-t-cyan-400 rounded-full animate-spin"></div>
          <span class="text-sm">Chargement…</span>
        </div>

      {:else if loadError}
        <div class="flex items-center gap-3 max-w-md mx-auto mt-16 p-4 card border-signal-red/20">
          <span class="text-signal-red text-xl">⚠</span>
          <p class="text-signal-red text-sm">{loadError}</p>
        </div>

      {:else if sortedAgentVolumes.length === 0}
        <div class="flex flex-col items-center justify-center h-64 gap-2 text-slate-600">
          <svg class="w-10 h-10 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
              d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
          </svg>
          <span class="text-sm">Aucun volume disponible</span>
        </div>

      {:else}
        <!-- Orphans filter toggle -->
        <div class="flex items-center justify-end mb-4">
          <button
            onclick={() => showOrphansOnlyVolumes = !showOrphansOnlyVolumes}
            class="text-xs px-2.5 py-1 rounded-lg border transition-colors
                   {showOrphansOnlyVolumes
                     ? 'bg-red-500/15 text-red-400 border-red-500/30'
                     : 'bg-white/[0.05] text-slate-400 border-white/[0.08] hover:bg-white/[0.09]'}"
          >
            {showOrphansOnlyVolumes ? "Tous les volumes" : "Orphelins seulement"}
          </button>
        </div>

        {#each sortedAgentVolumes as [agentId, agentVolumes]}
          {@const displayedVolumes = showOrphansOnlyVolumes ? agentVolumes.filter(v => v.is_orphan) : agentVolumes}
          {#if displayedVolumes.length > 0}
          {@const first = agentVolumes[0]}
          <section class="mb-8">

            <div class="flex items-center gap-2.5 mb-3 px-1">
              <button
                class="flex items-center gap-2.5 flex-1 min-w-0 text-left cursor-pointer group"
                onclick={() => toggleVolumes(agentId)}
                type="button"
              >
                <svg
                  class="w-3.5 h-3.5 text-slate-500 transition-transform duration-200 shrink-0
                         {collapsedVolumes[agentId] !== false ? '-rotate-90' : 'rotate-0'}"
                  fill="none" stroke="currentColor" viewBox="0 0 24 24"
                >
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" />
                </svg>
                <span class="dot-running"></span>
                <h2 class="font-semibold text-slate-200 text-sm group-hover:text-slate-100 transition-colors">
                  {first.alias || first.hostname}
                </h2>
                {#if first.alias}
                  <span class="hidden sm:inline font-mono text-xs text-slate-600">{first.hostname}</span>
                {/if}
                {#if first.ip_address}
                  <span class="hidden sm:inline font-mono text-xs text-slate-500 bg-abyss-700 px-2 py-0.5 rounded-full
                               border border-white/[0.06]">{first.ip_address}</span>
                {/if}
                <span class="text-xs text-slate-600 ml-auto">
                  {agentVolumes.length} volume{agentVolumes.length !== 1 ? "s" : ""}
                </span>
              </button>
              {#if agentVolumes.some(v => v.is_orphan)}
                <button
                  onclick={() => pruneAllVolumes(agentId, agentVolumes)}
                  class="shrink-0 text-xs px-2 py-1 rounded border border-red-500/30 text-red-400 hover:bg-red-500/10 transition-colors"
                >
                  Prune orphans
                </button>
              {/if}
            </div>

            {#if collapsedVolumes[agentId] === false}
            <div class="card overflow-hidden">
              <!-- Desktop table -->
              <table class="hidden md:table w-full text-sm">
                <thead>
                  <tr class="border-b border-white/[0.06] text-xs uppercase tracking-wider text-slate-600">
                    <th class="px-4 py-3 text-left font-medium">Nom</th>
                    <th class="px-4 py-3 text-left font-medium">Driver</th>
                    <th class="px-4 py-3 text-left font-medium">Mountpoint</th>
                    <th class="px-4 py-3 text-right font-medium">Actions</th>
                  </tr>
                </thead>
                <tbody>
                  {#each displayedVolumes.slice().sort((a, b) => a.name.localeCompare(b.name)) as vol (vol.name)}
                    <tr class="border-b border-white/[0.04] last:border-0 hover:bg-white/[0.025] transition-colors
                               {vol.is_orphan ? 'bg-red-500/[0.03]' : ''}">
                      <td class="px-4 py-3">
                        <div class="flex items-center gap-2">
                          <span class="font-mono text-xs text-slate-200 font-medium">{vol.name}</span>
                          {#if vol.is_orphan}
                            <span class="text-xs px-1.5 py-0.5 rounded bg-red-500/10 text-red-400 border border-red-500/20">orphan</span>
                          {/if}
                        </div>
                      </td>
                      <td class="px-4 py-3 text-xs text-slate-400">{vol.driver}</td>
                      <td class="px-4 py-3 font-mono text-xs text-slate-500 max-w-xs truncate"
                          title={vol.mountpoint}>{vol.mountpoint}</td>
                      <td class="px-4 py-3 text-right">
                        {#if vol.is_orphan}
                          <button
                            onclick={() => doDeleteVolume(agentId, vol.name)}
                            disabled={deletePending === vol.name}
                            class="text-xs px-2 py-1 rounded border border-red-500/30 text-red-400 hover:bg-red-500/10 transition-colors disabled:opacity-40"
                          >
                            {deletePending === vol.name ? "…" : "Delete"}
                          </button>
                        {/if}
                      </td>
                    </tr>
                  {/each}
                </tbody>
              </table>

              <!-- Mobile cards -->
              <div class="md:hidden divide-y divide-white/[0.04]">
                {#each displayedVolumes.slice().sort((a, b) => a.name.localeCompare(b.name)) as vol (vol.name)}
                  <div class="p-4 {vol.is_orphan ? 'bg-red-500/[0.03]' : ''}">
                    <div class="flex items-start justify-between gap-2 mb-1">
                      <div class="flex items-center gap-2 min-w-0">
                        <span class="font-mono text-xs font-medium text-slate-200 truncate">{vol.name}</span>
                        {#if vol.is_orphan}
                          <span class="shrink-0 text-xs px-1.5 py-0.5 rounded bg-red-500/10 text-red-400 border border-red-500/20">orphan</span>
                        {/if}
                      </div>
                      {#if vol.is_orphan}
                        <button
                          onclick={() => doDeleteVolume(agentId, vol.name)}
                          disabled={deletePending === vol.name}
                          class="shrink-0 text-xs px-2 py-1 rounded border border-red-500/30 text-red-400 hover:bg-red-500/10 transition-colors disabled:opacity-40"
                        >
                          {deletePending === vol.name ? "…" : "Delete"}
                        </button>
                      {/if}
                    </div>
                    <div class="flex items-center gap-3 text-xs text-slate-500 mt-1">
                      <span>{vol.driver}</span>
                      <span class="font-mono truncate">{vol.mountpoint}</span>
                    </div>
                  </div>
                {/each}
              </div>
            </div>
            {/if}

          </section>
          {/if}
        {/each}
      {/if}

    <!-- ═══════════════════════════════════════════════════
         NETWORKS TAB
    ════════════════════════════════════════════════════ -->
    {:else if activeTab === "networks"}
      {#if networks === null}
        <div class="flex flex-col items-center justify-center h-64 gap-3 text-slate-600">
          <div class="w-8 h-8 border-2 border-cyan-400/30 border-t-cyan-400 rounded-full animate-spin"></div>
          <span class="text-sm">Chargement…</span>
        </div>

      {:else if loadError}
        <div class="flex items-center gap-3 max-w-md mx-auto mt-16 p-4 card border-signal-red/20">
          <span class="text-signal-red text-xl">⚠</span>
          <p class="text-signal-red text-sm">{loadError}</p>
        </div>

      {:else if sortedAgentNetworks.length === 0}
        <div class="flex flex-col items-center justify-center h-64 gap-2 text-slate-600">
          <svg class="w-10 h-10 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
              d="M9 3H5a2 2 0 00-2 2v4m6-6h10a2 2 0 012 2v4M9 3v18m0 0h10a2 2 0 002-2V9M9 21H5a2 2 0 01-2-2V9m0 0h18" />
          </svg>
          <span class="text-sm">Aucun réseau disponible</span>
        </div>

      {:else}
        <!-- Orphans filter toggle -->
        <div class="flex items-center justify-end mb-4">
          <button
            onclick={() => showOrphansOnlyNetworks = !showOrphansOnlyNetworks}
            class="text-xs px-2.5 py-1 rounded-lg border transition-colors
                   {showOrphansOnlyNetworks
                     ? 'bg-red-500/15 text-red-400 border-red-500/30'
                     : 'bg-white/[0.05] text-slate-400 border-white/[0.08] hover:bg-white/[0.09]'}"
          >
            {showOrphansOnlyNetworks ? "Tous les réseaux" : "Orphelins seulement"}
          </button>
        </div>

        {#each sortedAgentNetworks as [agentId, agentNetworks]}
          {@const displayedNetworks = showOrphansOnlyNetworks ? agentNetworks.filter(n => n.is_orphan) : agentNetworks}
          {#if displayedNetworks.length > 0}
          {@const first = agentNetworks[0]}
          <section class="mb-8">

            <div class="flex items-center gap-2.5 mb-3 px-1">
              <button
                class="flex items-center gap-2.5 flex-1 min-w-0 text-left cursor-pointer group"
                onclick={() => toggleNetworks(agentId)}
                type="button"
              >
                <svg
                  class="w-3.5 h-3.5 text-slate-500 transition-transform duration-200 shrink-0
                         {collapsedNetworks[agentId] !== false ? '-rotate-90' : 'rotate-0'}"
                  fill="none" stroke="currentColor" viewBox="0 0 24 24"
                >
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" />
                </svg>
                <span class="dot-running"></span>
                <h2 class="font-semibold text-slate-200 text-sm group-hover:text-slate-100 transition-colors">
                  {first.alias || first.hostname}
                </h2>
                {#if first.alias}
                  <span class="hidden sm:inline font-mono text-xs text-slate-600">{first.hostname}</span>
                {/if}
                {#if first.ip_address}
                  <span class="hidden sm:inline font-mono text-xs text-slate-500 bg-abyss-700 px-2 py-0.5 rounded-full
                               border border-white/[0.06]">{first.ip_address}</span>
                {/if}
                <span class="text-xs text-slate-600 ml-auto">
                  {agentNetworks.length} réseau{agentNetworks.length !== 1 ? "x" : ""}
                </span>
              </button>
              {#if agentNetworks.some(n => n.is_orphan)}
                <button
                  onclick={() => pruneAllNetworks(agentId, agentNetworks)}
                  class="shrink-0 text-xs px-2 py-1 rounded border border-red-500/30 text-red-400 hover:bg-red-500/10 transition-colors"
                >
                  Prune orphans
                </button>
              {/if}
            </div>

            {#if collapsedNetworks[agentId] === false}
            <div class="card overflow-hidden">
              <!-- Desktop table -->
              <table class="hidden md:table w-full text-sm">
                <thead>
                  <tr class="border-b border-white/[0.06] text-xs uppercase tracking-wider text-slate-600">
                    <th class="px-4 py-3 text-left font-medium">Nom</th>
                    <th class="px-4 py-3 text-left font-medium">Driver</th>
                    <th class="px-4 py-3 text-left font-medium">Scope</th>
                    <th class="px-4 py-3 text-left font-medium">ID</th>
                    <th class="px-4 py-3 text-right font-medium">Actions</th>
                  </tr>
                </thead>
                <tbody>
                  {#each displayedNetworks.slice().sort((a, b) => a.name.localeCompare(b.name)) as net (net.id)}
                    <tr class="border-b border-white/[0.04] last:border-0 hover:bg-white/[0.025] transition-colors
                               {net.is_orphan ? 'bg-red-500/[0.03]' : ''}">
                      <td class="px-4 py-3">
                        <div class="flex items-center gap-2">
                          <span class="font-mono text-xs text-slate-200 font-medium">{net.name}</span>
                          {#if net.is_orphan}
                            <span class="text-xs px-1.5 py-0.5 rounded bg-red-500/10 text-red-400 border border-red-500/20">orphan</span>
                          {/if}
                        </div>
                      </td>
                      <td class="px-4 py-3 text-xs text-slate-400">{net.driver}</td>
                      <td class="px-4 py-3 text-xs">
                        <span class="px-1.5 py-0.5 rounded text-xs
                                     {net.scope === 'local'
                                       ? 'bg-slate-700/60 text-slate-400'
                                       : 'bg-cyan-400/10 text-cyan-400 border border-cyan-400/20'}">
                          {net.scope}
                        </span>
                      </td>
                      <td class="px-4 py-3 font-mono text-xs text-slate-600">{shortId(net.id)}</td>
                      <td class="px-4 py-3 text-right">
                        {#if net.is_orphan}
                          <button
                            onclick={() => doDeleteNetwork(agentId, net.id)}
                            disabled={deletePending === net.id}
                            class="text-xs px-2 py-1 rounded border border-red-500/30 text-red-400 hover:bg-red-500/10 transition-colors disabled:opacity-40"
                          >
                            {deletePending === net.id ? "…" : "Delete"}
                          </button>
                        {/if}
                      </td>
                    </tr>
                  {/each}
                </tbody>
              </table>

              <!-- Mobile cards -->
              <div class="md:hidden divide-y divide-white/[0.04]">
                {#each displayedNetworks.slice().sort((a, b) => a.name.localeCompare(b.name)) as net (net.id)}
                  <div class="p-4 {net.is_orphan ? 'bg-red-500/[0.03]' : ''}">
                    <div class="flex items-start justify-between gap-2 mb-1">
                      <div class="flex items-center gap-2 min-w-0">
                        <span class="font-mono text-xs font-medium text-slate-200 truncate">{net.name}</span>
                        {#if net.is_orphan}
                          <span class="shrink-0 text-xs px-1.5 py-0.5 rounded bg-red-500/10 text-red-400 border border-red-500/20">orphan</span>
                        {/if}
                      </div>
                      {#if net.is_orphan}
                        <button
                          onclick={() => doDeleteNetwork(agentId, net.id)}
                          disabled={deletePending === net.id}
                          class="shrink-0 text-xs px-2 py-1 rounded border border-red-500/30 text-red-400 hover:bg-red-500/10 transition-colors disabled:opacity-40"
                        >
                          {deletePending === net.id ? "…" : "Delete"}
                        </button>
                      {/if}
                    </div>
                    <div class="flex items-center gap-3 text-xs text-slate-500 mt-1">
                      <span>{net.driver}</span>
                      <span class="px-1.5 py-0.5 rounded
                                   {net.scope === 'local'
                                     ? 'bg-slate-700/60 text-slate-400'
                                     : 'bg-cyan-400/10 text-cyan-400 border border-cyan-400/20'}">{net.scope}</span>
                      <span class="font-mono">{shortId(net.id)}</span>
                    </div>
                  </div>
                {/each}
              </div>
            </div>
            {/if}

          </section>
          {/if}
        {/each}
      {/if}
    {/if}

  </main>

  <!-- Auto-update panel (position: fixed, hors du flux des tableaux) -->
  {#if autoUpdateOpen !== null && autoUpdatePanelPos !== null}
    {@const state = autoUpdateStates[autoUpdateOpen]}
    {@const parts = autoUpdateOpen.split('/')}
    {@const panelAgentId = parts[0]}
    {@const panelContainerId = parts.slice(1).join('/')}
    <div
      data-autoupdate-panel
      role="presentation"
      onclick={(e) => e.stopPropagation()}
      style="position: fixed; top: {autoUpdatePanelPos.top}px; right: {autoUpdatePanelPos.right}px;"
      class="z-50 w-64 bg-gray-800 border border-gray-700 rounded-lg shadow-xl p-3"
    >
      {#if state?.loading}
        <div class="flex items-center justify-center py-4 text-slate-500 text-xs gap-2">
          <div class="w-4 h-4 border border-slate-600 border-t-violet-400 rounded-full animate-spin"></div>
          Chargement…
        </div>

      {:else if !state?.policy}
        <p class="text-xs text-signal-red text-center py-2">Erreur de chargement</p>

      {:else}
        <!-- Toggle -->
        <div class="flex items-center justify-between mb-3">
          <span class="text-xs font-medium text-slate-300">Auto-update</span>
          <button
            type="button"
            aria-pressed={state.policy.enabled}
            aria-label="Activer/désactiver l'auto-update"
            onclick={() => toggleAutoUpdateEnabled(panelAgentId, panelContainerId)}
            class="relative w-9 h-5 rounded-full cursor-pointer transition-colors focus:outline-none
                   {state.policy.enabled ? 'bg-violet-500' : 'bg-gray-600'}
                   {state.saving ? 'opacity-60' : ''}"
          >
            <span
              class="absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white shadow
                     transition-transform duration-200
                     {state.policy.enabled ? 'translate-x-4' : 'translate-x-0'}"
            ></span>
          </button>
        </div>

        <!-- Interval -->
        <div class="mb-3">
          <label for="au-interval-{autoUpdateOpen}" class="text-xs text-slate-500 block mb-1">Intervalle</label>
          <select
            id="au-interval-{autoUpdateOpen}"
            disabled={!state.policy.enabled || state.saving}
            value={state.policy.interval_minutes}
            onchange={(e) => changeAutoUpdateInterval(panelAgentId, panelContainerId, Number((e.target as HTMLSelectElement).value))}
            class="w-full bg-gray-900 border border-gray-600 text-gray-300 text-xs
                   rounded-md px-2 py-1.5 focus:outline-none focus:border-violet-500
                   disabled:opacity-40 disabled:cursor-not-allowed"
          >
            {#each INTERVAL_OPTIONS as opt}
              <option value={opt.value}>{opt.label}</option>
            {/each}
          </select>
        </div>

        <!-- Bouton update now -->
        <div class="mb-3">
          <button
            type="button"
            disabled={updateNowPending === autoUpdateOpen || state.saving}
            onclick={() => doUpdateNow(panelAgentId, panelContainerId)}
            class="w-full px-3 py-1.5 rounded-md text-xs font-medium transition-all border
                   bg-violet-500/15 hover:bg-violet-500/25 text-violet-300 border-violet-500/30
                   disabled:opacity-40 disabled:cursor-not-allowed"
          >
            {updateNowPending === autoUpdateOpen ? "Mise à jour en cours…" : "Mettre à jour maintenant"}
          </button>
        </div>

        <!-- Info lines -->
        <div class="space-y-1 border-t border-gray-700 pt-2">
          <p class="text-gray-500 text-xs">
            Vérification : <span class="text-slate-400">{formatRelativeTime(state.policy.last_checked_at)}</span>
          </p>
          <p class="text-gray-500 text-xs">
            Mise à jour : <span class="text-slate-400">{formatRelativeTime(state.policy.last_updated_at)}</span>
          </p>
        </div>

        {#if state.saving}
          <p class="text-xs text-violet-400 mt-2 text-center">Enregistrement…</p>
        {/if}
      {/if}
    </div>
  {/if}

  <!-- Group auto-update panel -->
  {#if groupAutoUpdate !== null && groupAutoUpdatePanelPos !== null}
    <div
      data-group-autoupdate-panel
      role="presentation"
      onclick={(e) => e.stopPropagation()}
      style="position: fixed; top: {groupAutoUpdatePanelPos.top}px; right: {groupAutoUpdatePanelPos.right}px;"
      class="z-50 w-64 bg-gray-800 border border-gray-700 rounded-lg shadow-xl p-3"
    >
      <div class="flex items-center gap-1.5 mb-3 pb-2 border-b border-gray-700">
        <span class="text-xs font-medium text-slate-400">Projet :</span>
        <span class="text-xs font-mono text-slate-300 truncate">{groupAutoUpdate.projectName}</span>
      </div>
      {#if groupAutoUpdate.loading}
        <div class="flex items-center justify-center py-4 text-slate-500 text-xs gap-2">
          <div class="w-4 h-4 border border-slate-600 border-t-violet-400 rounded-full animate-spin"></div>
          Chargement…
        </div>
      {:else if !groupAutoUpdate.policy}
        <p class="text-xs text-signal-red text-center py-2">Erreur de chargement</p>
      {:else}
        <!-- Toggle -->
        <div class="flex items-center justify-between mb-3">
          <span class="text-xs font-medium text-slate-300">Auto-update</span>
          <button type="button"
            aria-pressed={groupAutoUpdate.policy.enabled}
            aria-label="Activer/désactiver l'auto-update du groupe"
            onclick={toggleGroupAutoUpdateEnabled}
            class="relative w-9 h-5 rounded-full cursor-pointer transition-colors focus:outline-none
                   {groupAutoUpdate.policy.enabled ? 'bg-violet-500' : 'bg-gray-600'}
                   {groupAutoUpdate.saving ? 'opacity-60' : ''}">
            <span class="absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white shadow transition-transform duration-200
                         {groupAutoUpdate.policy.enabled ? 'translate-x-4' : 'translate-x-0'}"></span>
          </button>
        </div>
        <!-- Interval -->
        <div class="mb-2">
          <label for="au-group-interval" class="text-xs text-slate-500 block mb-1">Intervalle</label>
          <select
            id="au-group-interval"
            disabled={!groupAutoUpdate.policy.enabled || groupAutoUpdate.saving}
            value={groupAutoUpdate.policy.interval_minutes}
            onchange={(e) => changeGroupAutoUpdateInterval(Number((e.target as HTMLSelectElement).value))}
            class="w-full bg-gray-900 border border-gray-600 text-gray-300 text-xs rounded-md px-2 py-1.5
                   focus:outline-none focus:border-violet-500 disabled:opacity-40 disabled:cursor-not-allowed"
          >
            {#each INTERVAL_OPTIONS as opt}
              <option value={opt.value}>{opt.label}</option>
            {/each}
          </select>
        </div>
        <p class="text-xs text-slate-600 text-center">Appliqué aux {groupAutoUpdate.containerIds.length} containers</p>
        {#if groupAutoUpdate.saving}
          <p class="text-xs text-violet-400 mt-1 text-center">Enregistrement…</p>
        {/if}
      {/if}
    </div>
  {/if}
</div>

{#snippet ActionBtn({ label, variant, loading, onclick }: {
  label: string;
  variant: "green" | "ghost" | "cyan";
  loading: boolean;
  onclick: () => void;
})}
  <button
    {onclick}
    disabled={loading}
    class="px-2.5 py-1 rounded-lg text-xs font-medium transition-all disabled:opacity-40
           {variant === 'green'
             ? 'bg-signal-green/10 hover:bg-signal-green/20 text-signal-green border border-signal-green/25'
             : variant === 'cyan'
               ? 'bg-signal-cyan/10 hover:bg-signal-cyan/20 text-signal-cyan border border-signal-cyan/25'
               : 'bg-white/[0.05] hover:bg-white/[0.09] text-slate-400 border border-white/[0.08]'}"
  >
    {loading ? "…" : label}
  </button>
{/snippet}

{#snippet AutoUpdateBtn(agentId: string, containerId: string)}
  {@const key = autoUpdateKey(agentId, containerId)}
  {@const isOpen = autoUpdateOpen === key}
  {@const state = autoUpdateStates[key]}
  <div class="relative">
    <button
      data-autoupdate-btn
      onclick={(e) => {
        e.stopPropagation();
        if (autoUpdateOpen !== key) {
          const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
          openAutoUpdate(agentId, containerId, { top: rect.bottom + 6, right: window.innerWidth - rect.right });
        } else {
          openAutoUpdate(agentId, containerId);
        }
      }}
      title="Auto-update"
      class="px-2 py-1 rounded-lg text-xs font-medium transition-all border
             {isOpen || state?.policy?.enabled
               ? 'bg-violet-500/20 text-violet-300 border-violet-500/40'
               : 'bg-white/[0.05] hover:bg-white/[0.09] text-slate-500 hover:text-slate-300 border-white/[0.08]'}"
    >
      <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
          d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
      </svg>
    </button>
  </div>
{/snippet}
