<script>
  let { page = $bindable("dashboard"), dark = false, toggleTheme = () => {}, logout = () => {}, sidebarOpen = $bindable(false), allowedPages = "*", allowedSidebarLinks = "*", userRole = "admin", sidebarOrder = null, onOrderChange = () => {} } = $props();

  function canAccess(id) {
    if (!id) return true;
    if (allowedPages === "*") return true;
    if (!Array.isArray(allowedPages)) return false;
    if (id === "info-engine") return allowedPages.includes("dashboard");
    return allowedPages.includes(id);
  }

  function canSeeSidebarLink(item) {
    if (allowedSidebarLinks === "*") return true;
    // Check by label or id
    const key = item.label || item.id;
    return Array.isArray(allowedSidebarLinks) && allowedSidebarLinks.includes(key);
  }

  const defaultLinks = [
    { id: "dashboard", label: "Overview", icon: "grid" },
    { id: "litellm", label: "LiteLLM", icon: "bolt" },
    { id: "opc", label: "OPC", icon: "kanban" },
    { id: "files", label: "Files", icon: "folder" },
    { id: "terminal", label: "Terminal", icon: "terminal" },
    { id: "security", label: "Security", icon: "shield" },
  ];

  let lanIp = $state("192.168.31.222");
  let tsIp = $state("100.78.131.124");
  let lanReachable = $state(false);
  let serviceUrls = $state({});

  if (typeof window !== "undefined") {
    fetch("/api/client-ip").then(r => r.json()).then(d => {
      if (d.lan) lanReachable = true;
    }).catch(() => {});

    // Fetch external service URLs from backend config
    fetch("/api/system/external-services").then(r => r.json()).then(d => {
      if (d.lan_ip) lanIp = d.lan_ip;
      if (d.ts_ip) tsIp = d.ts_ip;
      if (d.services) serviceUrls = d.services;
    }).catch(() => {});
  }

  const defaultMedia = [
    { label: "Navidrome", remoteHref: "https://music.jimmygan.com", icon: "music" },
    { label: "Jellyfin", remoteHref: "https://media.jimmygan.com", icon: "play" },
    { label: "Audiobookshelf", remoteHref: "https://books.jimmygan.com", icon: "headphones" },
    { label: "Immich", remoteHref: "https://photos.jimmygan.com", icon: "image" },
    { label: "t-youtube", remoteHref: "https://yt.jimmygan.com", icon: "youtube" },
    { label: "Media Management", remoteHref: "https://media.jimmygan.com", icon: "wrench" },
    { id: "transmission", label: "Transmission", icon: "download" },
  ];

  const defaultTools = [
    { label: "Hermes", remoteHref: "https://hermes-agent.nousresearch.com/docs", icon: "robot" },
    { id: "chat-digest", label: "Chat Digest", icon: "chat" },
    { id: "conversations", label: "Code Sessions", icon: "code" },
    { id: "gitea", label: "Repos", icon: "git" },
    { label: "Gitea Web", remoteHref: "https://git.jimmygan.com", icon: "git", external: true },
    { label: "Stirling PDF", remoteHref: "https://pdf.jimmygan.com", icon: "pdf", external: true },
    { label: "Vaultwarden", remoteHref: "https://vault.jimmygan.com", icon: "lock", external: true },
    { label: "n8n", remoteHref: "https://n8n.jimmygan.com", icon: "n8n", external: true },
    { label: "Speedtest", remoteHref: "https://speed.jimmygan.com", icon: "speedtest", external: true },
    { label: "OpenConnector", remoteHref: "https://connector.jimmygan.com", port: 3002, icon: "link", external: true },
  ];

  const defaultTools2 = [];

  function itemKey(item) { return item.id || item.label; }

  // All items indexed by key for cross-category lookup
  const allItems = new Map([...defaultLinks, ...defaultMedia, ...defaultTools, ...defaultTools2].map(i => [itemKey(i), i]));

  // Build category lists from saved order, supporting cross-category moves
  const categories = ["main", "media", "tools", "tools2"];
  const categoryDefaults = { main: defaultLinks, media: defaultMedia, tools: defaultTools, tools2: defaultTools2 };

  function buildLists(savedOrder) {
    if (!savedOrder) return { main: [...defaultLinks], media: [...defaultMedia], tools: [...defaultTools], tools2: [...defaultTools2] };

    const used = new Set();
    const result = { main: [], media: [], tools: [], tools2: [] };

    for (const cat of categories) {
      const keys = savedOrder[cat] || [];
      for (const k of keys) {
        if (allItems.has(k) && !used.has(k)) {
          result[cat].push(allItems.get(k));
          used.add(k);
        }
      }
    }

    // Append any new items not in saved order to their default category
    for (const cat of categories) {
      for (const item of categoryDefaults[cat]) {
        if (!used.has(itemKey(item))) {
          result[cat].push(item);
          used.add(itemKey(item));
        }
      }
    }

    return result;
  }

  let categoryLists = $derived(buildLists(sidebarOrder));
  let links = $derived(categoryLists.main);
  let media = $derived(categoryLists.media);
  let tools = $derived(categoryLists.tools);
  let tools2 = $derived(categoryLists.tools2);

  // Collapsed state for categories (persisted in sidebarOrder)
  let collapsed = $state(sidebarOrder?.collapsed ? { ...sidebarOrder.collapsed } : {});

  // Drag-and-drop state
  let dragCategory = $state(null);
  let dragIndex = $state(-1);
  let dropCategory = $state(null);
  let dropIndex = $state(-1);

  function handleDragStart(category, index, e) {
    dragCategory = category;
    dragIndex = index;
    e.dataTransfer.effectAllowed = "move";
    e.dataTransfer.setData("text/plain", "");
  }

  function handleDragOver(category, index, e) {
    e.preventDefault();
    e.dataTransfer.dropEffect = "move";
    dropCategory = category;
    dropIndex = index;
  }

  function handleDragEnd() {
    dragCategory = null;
    dragIndex = -1;
    dropCategory = null;
    dropIndex = -1;
  }

  function handleDrop(category, index, e) {
    e.preventDefault();
    if (dragCategory === null) { handleDragEnd(); return; }
    if (dragCategory === category && dragIndex === index) { handleDragEnd(); return; }
    const lists = { main: [...links], media: [...media], tools: [...tools], tools2: [...tools2] };
    const [moved] = lists[dragCategory].splice(dragIndex, 1);
    lists[category].splice(index, 0, moved);
    const newOrder = {
      main: lists.main.map(itemKey),
      media: lists.media.map(itemKey),
      tools: lists.tools.map(itemKey),
      tools2: lists.tools2.map(itemKey),
      collapsed,
    };
    onOrderChange(newOrder);
    handleDragEnd();
  }

  // Allow dropping at end of a category (on the section divider/header area)
  function handleCategoryDragOver(category, e) {
    e.preventDefault();
    e.dataTransfer.dropEffect = "move";
    dropCategory = category;
    dropIndex = -1;
  }

  function handleCategoryDrop(category, e) {
    e.preventDefault();
    if (dragCategory === null) { handleDragEnd(); return; }
    const lists = { main: [...links], media: [...media], tools: [...tools], tools2: [...tools2] };
    const [moved] = lists[dragCategory].splice(dragIndex, 1);
    lists[category].push(moved);
    const newOrder = {
      main: lists.main.map(itemKey),
      media: lists.media.map(itemKey),
      tools: lists.tools.map(itemKey),
      tools2: lists.tools2.map(itemKey),
      collapsed,
    };
    onOrderChange(newOrder);
    handleDragEnd();
  }

  function extHref(svc) {
    // Look up service URL from fetched config by label (lowercased, underscored)
    const key = svc.label ? svc.label.toLowerCase().replace(/\s+/g, "_") : "";
    const configUrl = serviceUrls[key];
    const remoteHref = configUrl || svc.remoteHref;

    if (lanReachable && svc.port) return `http://${lanIp}:${svc.port}`;
    if (remoteHref) return remoteHref;
    if (svc.port) {
      const host = /^\d+\.\d+\.\d+\.\d+$/.test(location.hostname) ? location.hostname : tsIp;
      return `http://${host}:${svc.port}`;
    }
    return svc.href;
  }

  function navigate(id) {
    page = id;
    sidebarOpen = false;
  }

  function dragClasses(category, index) {
    if (dragCategory === category && index === dragIndex) return "opacity-30";
    if (dropCategory === category && index === dropIndex) return "border-t-2 border-primary-500";
    return "";
  }

  function toggleCollapse(cat) {
    collapsed[cat] = !collapsed[cat];
    const newOrder = {
      main: links.map(itemKey),
      media: media.map(itemKey),
      tools: tools.map(itemKey),
      tools2: tools2.map(itemKey),
      collapsed,
    };
    onOrderChange(newOrder);
  }
</script>

{#snippet iconSvg(icon)}
  <span class="w-5 h-5 flex items-center justify-center">
    {#if icon === "grid"}
      <svg class="w-[18px] h-[18px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" /></svg>
    {:else if icon === "sparkles"}
      <svg class="w-[18px] h-[18px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3l1.8 4.2L18 9l-4.2 1.8L12 15l-1.8-4.2L6 9l4.2-1.8L12 3zm6 11l.9 2.1L21 17l-2.1.9L18 20l-.9-2.1L15 17l2.1-.9L18 14zM5 14l.9 2.1L8 17l-2.1.9L5 20l-.9-2.1L2 17l2.1-.9L5 14z" /></svg>
    {:else if icon === "box"}
      <svg class="w-[18px] h-[18px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" /></svg>
    {:else if icon === "bolt"}
      <svg class="w-[18px] h-[18px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M13 3L4 14h6l-1 7 9-11h-6l1-7z" /></svg>
    {:else if icon === "folder"}
      <svg class="w-[18px] h-[18px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" /></svg>
    {:else if icon === "terminal"}
      <svg class="w-[18px] h-[18px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
    {:else if icon === "shield"}
      <svg class="w-[18px] h-[18px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
    {:else if icon === "kanban"}
      <svg class="w-[18px] h-[18px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2zm8 0h-2a2 2 0 00-2 2v8a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2z" /></svg>
    {:else if icon === "music"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2z" /></svg>
    {:else if icon === "play"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" /><path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
    {:else if icon === "headphones"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M3 18v-6a9 9 0 0118 0v6M3 18a3 3 0 003 3h0a3 3 0 003-3v-2a3 3 0 00-3-3h0a3 3 0 00-3 3v2zm18 0a3 3 0 01-3 3h0a3 3 0 01-3-3v-2a3 3 0 013-3h0a3 3 0 013 3v2z" /></svg>
    {:else if icon === "image"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" 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>
    {:else if icon === "download"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10" /></svg>
    {:else if icon === "openclaw"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" /></svg>
    {:else if icon === "chat"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a2 2 0 01-2-2v-1m0-3V6a2 2 0 012-2h8a2 2 0 012 2v3a2 2 0 01-2 2H9l-4 4V9z" /></svg>
    {:else if icon === "code"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /></svg>
    {:else if icon === "git"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /></svg>
    {:else if icon === "pdf"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" /></svg>
    {:else if icon === "lock"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg>
    {:else if icon === "speedtest"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
    {:else if icon === "users"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" /></svg>
    {:else if icon === "n8n"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" /><circle cx="8" cy="6" r="1.5" fill="currentColor"/><circle cx="16" cy="12" r="1.5" fill="currentColor"/><circle cx="10" cy="18" r="1.5" fill="currentColor"/></svg>
    {:else if icon === "youtube"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" /><path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /><path stroke-linecap="round" stroke-linejoin="round" d="M8 3l8 3-8 3" /></svg>
    {:else if icon === "wrench"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M11.42 15.17l-7.05 7.05a2 2 0 01-2.83 0l-.09-.09a2 2 0 010-2.83l7.05-7.05m4.24-4.24l1.41-1.41a4 4 0 015.66 0l.09.09a4 4 0 010 5.66l-1.41 1.41M14 14l-3-3m2 8l5-5" /></svg>
    {:else if icon === "robot"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M12 2v4M8 12h8M8 16h4m-2-8h0m0 0c-4 0-7 2-7 6s3 6 7 6 7-2 7-6-3-6-7-6zM6 8V6m12 2V6" /></svg>
    {:else if icon === "link"}
      <svg class="w-[16px] h-[16px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" /></svg>
    {/if}
  </span>
{/snippet}

<!-- Mobile overlay -->
{#if sidebarOpen}
  <button class="fixed inset-0 bg-black/40 z-40 md:hidden" onclick={() => sidebarOpen = false} aria-label="Close sidebar"></button>
{/if}

<aside class="fixed inset-y-0 left-0 z-50 w-[240px] h-screen flex flex-col shrink-0 border-r border-surface-200 dark:border-surface-700 bg-white dark:bg-surface-800 transition-transform duration-200 md:static md:translate-x-0 {sidebarOpen ? 'translate-x-0' : '-translate-x-full'}">
  <!-- Logo -->
  <div class="px-5 py-5 flex items-center gap-3">
    <div class="w-8 h-8 rounded-lg bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center shadow-md">
      <svg class="w-4 h-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2" /></svg>
    </div>
    <div>
      <h1 class="text-sm font-bold tracking-tight text-surface-900 dark:text-white">NAS Dashboard</h1>
      <p class="text-[10px] text-surface-400 font-medium">DS224+ · Synology</p>
    </div>
  </div>

  <!-- Nav Links -->
  <nav class="flex-1 px-3 mt-2 overflow-y-auto">
    {#each [["main", "Main", links], ["media", "Media", media], ["tools", "Tools", tools], ["tools2", "Tools 2", tools2]] as [cat, label, items], ci}
      {#if ci > 0}
        <div class="my-4 border-t border-surface-200 dark:border-surface-700"></div>
      {/if}
      <button
        class="w-full text-left text-[10px] font-semibold uppercase tracking-wider text-surface-400 px-3 mb-2 flex items-center justify-between {cat !== 'main' ? 'cursor-pointer' : ''}"
        ondragover={(e) => handleCategoryDragOver(cat, e)}
        ondrop={(e) => handleCategoryDrop(cat, e)}
        onclick={() => cat !== 'main' && toggleCollapse(cat)}
        disabled={cat === 'main'}
      >
        {label}
        {#if cat !== "main"}
          <svg class="w-3 h-3 opacity-30 hover:opacity-50 transition-all duration-150 {collapsed[cat] ? '-rotate-90' : ''}" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" /></svg>
        {/if}
      </button>
      {#if cat === "main" || !collapsed[cat]}
      {#if items.length === 0}
        <div
          class="px-3 py-3 mx-1 rounded-lg border border-dashed text-[11px] text-center transition-colors duration-150
            {dropCategory === cat ? 'border-primary-400 bg-primary-50 dark:bg-primary-900/20 text-primary-500' : 'border-surface-300 dark:border-surface-600 text-surface-400'}"
          ondragover={(e) => handleCategoryDragOver(cat, e)}
          ondrop={(e) => handleCategoryDrop(cat, e)}
        >
          Drop items here
        </div>
      {/if}
      {#each items as item, i}
        {#if canAccess(item.id) && canSeeSidebarLink(item)}
          {#if item.external || item.remoteHref || item.port}
            <a
              href={extHref(item)}
              target="_blank"
              rel="noopener"
              draggable="true"
              ondragstart={(e) => handleDragStart(cat, i, e)}
              ondragover={(e) => handleDragOver(cat, i, e)}
              ondrop={(e) => handleDrop(cat, i, e)}
              ondragend={handleDragEnd}
              class="w-full text-left px-3 py-2 rounded-lg text-[13px] font-medium flex items-center gap-2.5 mb-0.5 text-surface-500 hover:bg-surface-100 dark:hover:bg-surface-700 hover:text-surface-700 dark:hover:text-surface-200 dark:text-surface-400 transition-all duration-150 group {dragClasses(cat, i)}"
            >
              <svg class="w-3 h-3 opacity-0 group-hover:opacity-30 -ml-1 mr-0.5 shrink-0 cursor-grab" fill="currentColor" viewBox="0 0 24 24"><circle cx="9" cy="6" r="2"/><circle cx="15" cy="6" r="2"/><circle cx="9" cy="12" r="2"/><circle cx="15" cy="12" r="2"/><circle cx="9" cy="18" r="2"/><circle cx="15" cy="18" r="2"/></svg>
              {@render iconSvg(item.icon)}
              {item.label}
              <svg class="w-3 h-3 ml-auto opacity-40" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg>
            </a>
          {:else}
            <button
              draggable="true"
              ondragstart={(e) => handleDragStart(cat, i, e)}
              ondragover={(e) => handleDragOver(cat, i, e)}
              ondrop={(e) => handleDrop(cat, i, e)}
              ondragend={handleDragEnd}
              class="w-full text-left px-3 py-2.5 rounded-lg text-[13px] font-medium flex items-center gap-2.5 mb-0.5 transition-all duration-150 group
                {page === item.id
                  ? 'bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 shadow-sm'
                  : 'text-surface-500 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-700 hover:text-surface-700 dark:hover:text-surface-200'
                } {dragClasses(cat, i)}"
              onclick={() => navigate(item.id)}
            >
              <svg class="w-3 h-3 opacity-0 group-hover:opacity-30 -ml-1 mr-0.5 shrink-0 cursor-grab" fill="currentColor" viewBox="0 0 24 24"><circle cx="9" cy="6" r="2"/><circle cx="15" cy="6" r="2"/><circle cx="9" cy="12" r="2"/><circle cx="15" cy="12" r="2"/><circle cx="9" cy="18" r="2"/><circle cx="15" cy="18" r="2"/></svg>
              {@render iconSvg(item.icon)}
              {item.label}
            </button>
          {/if}
        {/if}
      {/each}
      {/if}
    {/each}
  </nav>

  <!-- Bottom actions -->
  <div class="px-3 pb-3 pt-2 border-t border-surface-200 dark:border-surface-700">
    {#if userRole === "admin"}
    <button
      class="w-full px-3 py-2 rounded-lg text-[13px] font-medium flex items-center gap-2.5 text-surface-500 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-700 transition-all duration-150 mb-1 {page === 'settings' ? 'bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 shadow-sm' : ''}"
      onclick={() => navigate('settings')}
    >
      <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
      Settings
    </button>
    {/if}
    <div class="flex items-center gap-1">
      <button
        onclick={logout}
        class="flex-1 px-3 py-2 rounded-lg text-[13px] font-medium flex items-center gap-2.5 text-surface-500 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-700 transition-all duration-150 hover:text-red-600 dark:hover:text-red-400"
      >
        <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" /></svg>
        Sign out
      </button>
      <button
        onclick={toggleTheme}
        class="p-2 rounded-lg text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-700 hover:text-surface-600 dark:hover:text-surface-300 transition-all duration-150"
        title={dark ? 'Light Mode' : 'Dark Mode'}
      >
        {#if dark}
          <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg>
        {:else}
          <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>
        {/if}
      </button>
    </div>
  </div>
</aside>
