import { Outlet, useNavigate, useLocation } from 'react-router';
import { config } from '../config';
import {
  AppShell,
  Sidebar,
  SidebarTree,
  StatusDot,
  TopBar,
  SearchTrigger,
  AutoRefreshToggle,
  ButtonGroup,
  TimeRangeDropdown,
  CommandPalette,
  CommandPaletteProvider,
  GlobalFilterProvider,
  ToastProvider,
  BreadcrumbProvider,
  useCommandPalette,
  useGlobalFilters,
  useStarred,
} from '@cameleer/design-system';
import type { SearchResult, SidebarTreeNode, DropdownItem, ButtonGroupItem, ExchangeStatus } from '@cameleer/design-system';
import sidebarLogo from '@cameleer/design-system/assets/cameleer-logo.svg';
import { Box, Settings, FileText, ChevronRight, Square, Pause, Star, X, User, Plus, EyeOff, Bell } from 'lucide-react';
import { AboutMeDialog } from './AboutMeDialog';
import { NotificationBell } from './NotificationBell';
import css from './LayoutShell.module.css';
import { useQueryClient } from '@tanstack/react-query';
import { useCatalog } from '../api/queries/catalog';
import { useAgents } from '../api/queries/agents';
import { useSearchExecutions, useAttributeKeys } from '../api/queries/executions';
import { useUsers, useGroups, useRoles } from '../api/queries/admin/rbac';
import { useEnvironments } from '../api/queries/admin/environments';
import { useAlerts } from '../api/queries/alerts';
import { useAlertRules } from '../api/queries/alertRules';
import type { UserDetail, GroupDetail, RoleDetail } from '../api/queries/admin/rbac';
import { useAuthStore, useIsAdmin, useCanControl } from '../auth/auth-store';
import { useEnvironmentStore } from '../api/environment-store';
import { useState, useMemo, useCallback, useEffect, useRef, createElement } from 'react';
import type { ReactNode } from 'react';
import { ContentTabs } from './ContentTabs';
import { EnvironmentSwitcherButton } from './EnvironmentSwitcherButton';
import { EnvironmentSwitcherModal } from './EnvironmentSwitcherModal';
import { envColorVar } from './env-colors';
import { useScope } from '../hooks/useScope';
import { formatDuration } from '../utils/format-utils';
import { parseFacetQuery, formatAttrParam } from '../utils/attribute-filter';
import {
  buildAppTreeNodes,
  buildAdminTreeNodes,
  buildAlertsTreeNodes,
  formatCount,
  readCollapsed,
  writeCollapsed,
} from './sidebar-utils';
import { useServerCapabilities } from '../api/queries/capabilities';
import type { SidebarApp } from './sidebar-utils';

/* ------------------------------------------------------------------ */
/*  Search data builder                                               */
/* ------------------------------------------------------------------ */

function buildSearchData(
  catalog: any[] | undefined,
  agents: any[] | undefined,
  attrKeys: string[] | undefined,
): SearchResult[] {
  if (!catalog) return [];
  const results: SearchResult[] = [];

  for (const app of catalog) {
    const slug = app.slug || app.appId;
    const name = app.displayName || slug;
    const liveAgents = (app.agents || []).filter((a: any) => a.status === 'live').length;
    results.push({
      id: slug,
      category: 'application',
      title: name,
      badges: [{ label: (app.health || 'unknown').toUpperCase(), color: healthToSearchColor(app.health) }],
      meta: `${(app.routes || []).length} routes · ${(app.agents || []).length} agents (${liveAgents} live) · ${(app.exchangeCount ?? 0).toLocaleString()} exchanges`,
      path: `/exchanges/${slug}`,
    });

    for (const route of (app.routes || [])) {
      results.push({
        id: `${slug}/${route.routeId}`,
        category: 'route',
        title: route.routeId,
        badges: [{ label: name }],
        meta: `${(route.exchangeCount ?? 0).toLocaleString()} exchanges`,
        path: `/exchanges/${slug}/${route.routeId}`,
      });
    }
  }

  if (agents) {
    for (const agent of agents) {
      results.push({
        id: agent.instanceId,
        category: 'agent',
        title: agent.displayName,
        badges: [{ label: (agent.status || 'unknown').toUpperCase(), color: healthToSearchColor((agent.status || '').toLowerCase()) }],
        meta: `${agent.applicationId} · ${agent.version || ''}${agent.tps != null ? ` · ${agent.tps.toFixed(1)} msg/s` : ''}`,
        path: `/runtime/${agent.applicationId}/${agent.instanceId}`,
      });
    }
  }

  if (attrKeys) {
    for (const key of attrKeys) {
      results.push({
        id: `attr-key-${key}`,
        category: 'attribute',
        title: key,
        meta: 'attribute key — filter list',
        // Path carries the facet in query-string form; handlePaletteSelect routes
        // attribute results to the current scope, so the leading segment below is
        // only used as a fallback when no scope is active.
        path: `/exchanges?attr=${encodeURIComponent(key)}`,
      });
    }
  }

  return results;
}

function buildAdminSearchData(
  users: UserDetail[] | undefined,
  groups: GroupDetail[] | undefined,
  roles: RoleDetail[] | undefined,
): SearchResult[] {
  const results: SearchResult[] = [];

  if (users) {
    for (const u of users) {
      results.push({
        id: `user:${u.userId}`,
        category: 'user',
        title: u.displayName || u.userId,
        meta: u.userId,
        path: '/admin/rbac',
      });
    }
  }

  if (groups) {
    for (const g of groups) {
      results.push({
        id: `group:${g.id}`,
        category: 'group',
        title: g.name,
        meta: g.parentGroupId ? `parent: ${g.parentGroupId}` : 'top-level group',
        path: '/admin/rbac',
      });
    }
  }

  if (roles) {
    for (const r of roles) {
      results.push({
        id: `role:${r.id}`,
        category: 'role',
        title: r.name,
        meta: r.scope,
        path: '/admin/rbac',
      });
    }
  }

  return results;
}

function buildAlertSearchData(
  alerts: any[] | undefined,
  rules: any[] | undefined,
): SearchResult[] {
  const results: SearchResult[] = [];
  if (alerts) {
    for (const a of alerts) {
      results.push({
        id: `alert:${a.id}`,
        category: 'alert',
        title: a.title ?? '(untitled)',
        badges: [
          { label: a.severity, color: severityToSearchColor(a.severity) },
          { label: a.state, color: stateToSearchColor(a.state) },
        ],
        meta: `${a.firedAt ?? ''}${a.silenced ? ' · silenced' : ''}`,
        path: `/alerts/inbox/${a.id}`,
      });
    }
  }
  if (rules) {
    for (const r of rules) {
      results.push({
        id: `rule:${r.id}`,
        category: 'alertRule',
        title: r.name,
        badges: [
          { label: r.severity, color: severityToSearchColor(r.severity) },
          { label: r.conditionKind, color: 'auto' },
          ...(r.enabled ? [] : [{ label: 'DISABLED', color: 'warning' as const }]),
        ],
        meta: `${r.evaluationIntervalSeconds}s · ${r.targets?.length ?? 0} targets`,
        path: `/alerts/rules/${r.id}`,
      });
    }
  }
  return results;
}

function severityToSearchColor(s: string): string {
  if (s === 'CRITICAL') return 'error';
  if (s === 'WARNING') return 'warning';
  return 'auto';
}

function stateToSearchColor(s: string): string {
  if (s === 'FIRING') return 'error';
  if (s === 'ACKNOWLEDGED') return 'warning';
  if (s === 'RESOLVED') return 'success';
  return 'auto';
}

function healthToSearchColor(health: string): string {
  switch (health) {
    case 'live': return 'success';
    case 'stale': return 'warning';
    case 'dead': return 'error';
    default: return 'auto';
  }
}

function statusToColor(status: string): string {
  switch (status) {
    case 'COMPLETED': return 'success';
    case 'FAILED': return 'error';
    case 'RUNNING': return 'running';
    default: return 'warning';
  }
}

function useDebouncedValue<T>(value: T, delayMs: number): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delayMs);
    return () => clearTimeout(timer);
  }, [value, delayMs]);
  return debounced;
}

/* ------------------------------------------------------------------ */
/*  Icon factories                                                    */
/* ------------------------------------------------------------------ */

function makeStatusDot(health: string) {
  return createElement(StatusDot, { variant: health as any });
}

function makeChevron() {
  return createElement(ChevronRight, { size: 14 });
}

function makeStopIcon() {
  return createElement(Square, { size: 12, style: { color: 'var(--error)' } });
}

function makePauseIcon() {
  return createElement(Pause, { size: 12, style: { color: 'var(--amber)' } });
}

/* ------------------------------------------------------------------ */
/*  Starred items                                                     */
/* ------------------------------------------------------------------ */

interface StarredItem {
  starKey: string;
  label: string;
  icon?: ReactNode;
  path: string;
  parentApp?: string;
}

function collectStarredItems(apps: SidebarApp[], starredIds: Set<string>): StarredItem[] {
  const items: StarredItem[] = [];
  for (const app of apps) {
    if (starredIds.has(`app:${app.id}`)) {
      items.push({ starKey: `app:${app.id}`, label: app.name, icon: makeStatusDot(app.health), path: `/apps/${app.id}` });
    }
    for (const route of app.routes) {
      const key = `route:${app.id}/${route.id}`;
      if (starredIds.has(key)) {
        items.push({ starKey: key, label: route.name, path: `/apps/${app.id}/${route.id}`, parentApp: app.name });
      }
    }
  }
  return items;
}

function StarredList({ items, onNavigate, onRemove }: { items: StarredItem[]; onNavigate: (path: string) => void; onRemove: (key: string) => void }) {
  if (items.length === 0) return null;
  return (
    <div className={css.starredList}>
      {items.map((item) => (
        <div
          key={item.starKey}
          className={css.starredItem}
          onClick={() => onNavigate(item.path)}
          role="button"
          tabIndex={0}
          onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') onNavigate(item.path); }}
        >
          {item.icon && <span className={css.starredIconWrap}>{item.icon}</span>}
          <span className={css.starredLabel}>
            {item.label}
            {item.parentApp && <span className={css.starredParentApp}>{item.parentApp}</span>}
          </span>
          <button
            className={css.starredRemoveBtn}
            onClick={(e) => { e.stopPropagation(); onRemove(item.starKey); }}
            aria-label={`Remove ${item.label} from starred`}
          >
            <X size={10} />
          </button>
        </div>
      ))}
    </div>
  );
}

/* ------------------------------------------------------------------ */
/*  Section state keys                                                */
/* ------------------------------------------------------------------ */

const STATUS_ITEMS: ButtonGroupItem[] = [
  { value: 'completed', label: 'Completed', color: 'var(--success)' },
  { value: 'warning', label: 'Warning', color: 'var(--warning)' },
  { value: 'failed', label: 'Failed', color: 'var(--error)' },
  { value: 'running', label: 'Running', color: 'var(--running)' },
]

const SK_APPS = 'sidebar:section:apps';
const SK_ADMIN = 'sidebar:section:admin';
const SK_ALERTS = 'sidebar:section:alerts';
const SK_COLLAPSED = 'sidebar:collapsed';

/* ------------------------------------------------------------------ */
/*  Main layout content                                               */
/* ------------------------------------------------------------------ */

function LayoutContent() {
  const navigate = useNavigate();
  const location = useLocation();
  const queryClient = useQueryClient();
  const globalFilters = useGlobalFilters();
  const { timeRange, autoRefresh, refreshTimeRange } = globalFilters;

  // --- Server capabilities ------------------------------------------
  const { data: capabilities } = useServerCapabilities();

  // --- Role checks ----------------------------------------------------
  const isAdmin = useIsAdmin();
  const canControl = useCanControl();

  // --- Environment filtering -----------------------------------------
  const selectedEnv = useEnvironmentStore((s) => s.environment);
  const setSelectedEnvRaw = useEnvironmentStore((s) => s.setEnvironment);

  const catalogFrom = timeRange.start.toISOString();
  const catalogTo = timeRange.end.toISOString();
  const { data: catalog } = useCatalog(selectedEnv, catalogFrom, catalogTo);
  // Env is always required now (path-based endpoint). For cross-env "all agents"
  // we'd need a separate flat endpoint; sidebar uses env-filtered list directly.
  const { data: agents } = useAgents();  // env pulled from store internally
  const allAgents = agents;
  const { data: attributeKeys } = useAttributeKeys();
  const { data: envRecords = [] } = useEnvironments();

  // Open alerts + rules for CMD-K (env-scoped).
  const { data: cmdkAlerts } = useAlerts({ state: ['FIRING'], acked: false, limit: 100 });
  const { data: cmdkRules } = useAlertRules();

  // Merge environments from both the environments table and agent heartbeats
  const environments: string[] = useMemo(() => {
    const envSet = new Set<string>();
    for (const e of envRecords) envSet.add(e.slug);
    if (allAgents) {
      for (const a of allAgents as any[]) {
        envSet.add(a.environmentId || 'default');
      }
    }
    if (envSet.size === 0) envSet.add('default');
    return [...envSet].sort();
  }, [allAgents, envRecords]);

  // --- Admin search data (only fetched on admin pages) ----------------
  const isAdminPage = location.pathname.startsWith('/admin');
  const isAlertsPage = location.pathname.startsWith('/alerts');
  const { data: adminUsers } = useUsers(isAdminPage);
  const { data: adminGroups } = useGroups(isAdminPage);
  const { data: adminRoles } = useRoles(isAdminPage);

  const { username, logout } = useAuthStore();
  const { open: paletteOpen, setOpen: setPaletteOpen } = useCommandPalette();
  const { scope, setTab } = useScope();

  // --- Starred items ------------------------------------------------
  const { starredIds, isStarred, toggleStar } = useStarred();

  // --- Sidebar collapse ---------------------------------------------
  const [sidebarCollapsed, setSidebarCollapsed] = useState(() => readCollapsed(SK_COLLAPSED, false));
  const handleCollapseToggle = useCallback(() => {
    setSidebarCollapsed((prev) => {
      writeCollapsed(SK_COLLAPSED, !prev);
      return !prev;
    });
  }, []);

  // --- Sidebar filter -----------------------------------------------
  const [filterQuery, setFilterQuery] = useState('');
  const [hideEmptyRoutes, setHideEmptyRoutes] = useState(() => readCollapsed('sidebar:hideEmptyRoutes', false));
  const [hideOfflineApps, setHideOfflineApps] = useState(() => readCollapsed('sidebar:hideOfflineApps', false));

  const toggleHideEmptyRoutes = useCallback(() => {
    setHideEmptyRoutes((prev) => { writeCollapsed('sidebar:hideEmptyRoutes', !prev); return !prev; });
  }, []);
  const toggleHideOfflineApps = useCallback(() => {
    setHideOfflineApps((prev) => { writeCollapsed('sidebar:hideOfflineApps', !prev); return !prev; });
  }, []);

  const setSelectedEnv = useCallback((env: string | undefined) => {
    setSelectedEnvRaw(env);
    setFilterQuery('');
    if (location.search) {
      navigate(location.pathname, { replace: true });
    }
    queryClient.invalidateQueries();
  }, [setSelectedEnvRaw, navigate, location.pathname, location.search, queryClient]);

  // --- Env switcher modal -------------------------------------------
  const [switcherOpen, setSwitcherOpen] = useState(false);

  // Force-open the switcher when we have envs loaded but no valid selection.
  // This replaces the old "All Envs" fallback: every session must pick one.
  const selectionInvalid =
    envRecords.length > 0 &&
    (selectedEnv === undefined || !envRecords.some((e) => e.slug === selectedEnv));
  const switcherForced = selectionInvalid;
  useEffect(() => {
    if (selectionInvalid) {
      if (selectedEnv !== undefined) setSelectedEnvRaw(undefined);
      setSwitcherOpen(true);
    }
  }, [selectionInvalid, selectedEnv, setSelectedEnvRaw]);

  const currentEnvRecord = envRecords.find((e) => e.slug === selectedEnv);
  const envBarColor = envColorVar(currentEnvRecord?.color);

  // --- Section open states ------------------------------------------
  const [appsOpen, setAppsOpen] = useState(() => (isAdminPage || isAlertsPage) ? false : readCollapsed(SK_APPS, true));
  const [adminOpen, setAdminOpen] = useState(() => isAdminPage ? true : readCollapsed(SK_ADMIN, false));
  const [alertsOpen, setAlertsOpen] = useState(() => isAlertsPage ? true : readCollapsed(SK_ALERTS, false));
  const [starredOpen, setStarredOpen] = useState(true);

  // Accordion: entering admin collapses apps + starred; leaving restores
  const opsStateRef = useRef({ apps: appsOpen, starred: starredOpen });
  const prevAdminRef = useRef(isAdminPage);
  useEffect(() => {
    if (isAdminPage && !prevAdminRef.current) {
      opsStateRef.current = { apps: appsOpen, starred: starredOpen };
      setAppsOpen(false);
      setStarredOpen(false);
      setAdminOpen(true);
    } else if (!isAdminPage && prevAdminRef.current) {
      setAppsOpen(opsStateRef.current.apps);
      setStarredOpen(opsStateRef.current.starred);
      setAdminOpen(false);
    }
    prevAdminRef.current = isAdminPage;
  }, [isAdminPage]); // eslint-disable-line react-hooks/exhaustive-deps

  // Accordion: entering alerts collapses apps + admin + starred; leaving restores
  const opsAlertsStateRef = useRef({ apps: appsOpen, admin: adminOpen, starred: starredOpen });
  const prevAlertsRef = useRef(isAlertsPage);
  useEffect(() => {
    if (isAlertsPage && !prevAlertsRef.current) {
      opsAlertsStateRef.current = { apps: appsOpen, admin: adminOpen, starred: starredOpen };
      setAppsOpen(false);
      setAdminOpen(false);
      setStarredOpen(false);
      setAlertsOpen(true);
    } else if (!isAlertsPage && prevAlertsRef.current) {
      setAppsOpen(opsAlertsStateRef.current.apps);
      setAdminOpen(opsAlertsStateRef.current.admin);
      setStarredOpen(opsAlertsStateRef.current.starred);
      setAlertsOpen(false);
    }
    prevAlertsRef.current = isAlertsPage;
  }, [isAlertsPage]); // eslint-disable-line react-hooks/exhaustive-deps

  const toggleAlerts = useCallback(() => {
    if (!isAlertsPage) {
      navigate('/alerts/inbox');
      return;
    }
    setAlertsOpen((prev) => {
      writeCollapsed(SK_ALERTS, !prev);
      return !prev;
    });
  }, [isAlertsPage, navigate]);

  const toggleApps = useCallback(() => {
    if (isAdminPage) {
      navigate('/exchanges');
      return;
    }
    if (appsOpen) {
      // Already open — navigate to all applications
      if (!autoRefresh) {
        refreshTimeRange();
        queryClient.invalidateQueries();
      }
      navigate(`/${scope.tab}`);
    } else {
      setAppsOpen(true);
      writeCollapsed(SK_APPS, true);
    }
  }, [isAdminPage, appsOpen, navigate, scope.tab, autoRefresh, refreshTimeRange, queryClient]);

  const toggleAdmin = useCallback(() => {
    if (!isAdminPage) {
      navigate('/admin/rbac');
      return;
    }
    setAdminOpen((prev) => {
      writeCollapsed(SK_ADMIN, !prev);
      return !prev;
    });
  }, [isAdminPage, navigate]);

  // --- Build SidebarApp[] from catalog ------------------------------
  const sidebarApps: SidebarApp[] = useMemo(() => {
    if (!catalog) return [];
    const cmp = (a: string, b: string) => a.localeCompare(b);
    return [...catalog]
      .sort((a: any, b: any) => cmp(a.slug, b.slug))
      .map((app: any) => ({
        id: app.slug,
        name: app.displayName || app.slug,
        health: (app.health === 'offline' ? 'dead' : app.health) as SidebarApp['health'],
        healthTooltip: app.healthTooltip,
        exchangeCount: app.exchangeCount,
        routes: [...(app.routes || [])]
          .sort((a: any, b: any) => cmp(a.routeId, b.routeId))
          .map((r: any) => ({
            id: r.routeId,
            name: r.routeId,
            exchangeCount: r.exchangeCount,
            routeState: r.routeState ?? undefined,
          })),
        agents: [],
      }));
  }, [catalog]);

  // --- Apply sidebar filters -----------------------------------------
  const filteredSidebarApps: SidebarApp[] = useMemo(() => {
    let apps = sidebarApps;
    if (hideOfflineApps) {
      apps = apps.filter((a) => a.health !== 'dead' && a.health !== 'stale');
    }
    if (hideEmptyRoutes) {
      apps = apps
        .map((a) => ({
          ...a,
          routes: a.routes.filter((r) => r.exchangeCount > 0),
        }))
        .filter((a) => a.exchangeCount > 0 || a.routes.length > 0);
    }
    return apps;
  }, [sidebarApps, hideOfflineApps, hideEmptyRoutes]);

  // --- Tree nodes ---------------------------------------------------
  const appTreeNodes: SidebarTreeNode[] = useMemo(
    () => buildAppTreeNodes(filteredSidebarApps, makeStatusDot, makeChevron, makeStopIcon, makePauseIcon),
    [filteredSidebarApps],
  );

  const adminTreeNodes: SidebarTreeNode[] = useMemo(
    () => buildAdminTreeNodes({ infrastructureEndpoints: capabilities?.infrastructureEndpoints }),
    [capabilities?.infrastructureEndpoints],
  );

  const alertsTreeNodes: SidebarTreeNode[] = useMemo(
    () => buildAlertsTreeNodes(),
    [],
  );

  // --- Starred items ------------------------------------------------
  const starredItems = useMemo(
    () => collectStarredItems(sidebarApps, starredIds),
    [sidebarApps, starredIds],
  );

  // --- Reveal path for SidebarTree auto-expand ----------------------
  const sidebarRevealPath = (location.state as any)?.sidebarReveal ?? null;

  useEffect(() => {
    if (!sidebarRevealPath) return;
    if (sidebarRevealPath.startsWith('/apps') && !appsOpen) setAppsOpen(true);
    if (sidebarRevealPath.startsWith('/admin') && !adminOpen) setAdminOpen(true);
    if (sidebarRevealPath.startsWith('/alerts') && !alertsOpen) setAlertsOpen(true);
  }, [sidebarRevealPath]); // eslint-disable-line react-hooks/exhaustive-deps

  // Normalize path so sidebar highlights the app regardless of which tab is active.
  // Sidebar nodes use /exchanges/{slug} paths, so map /dashboard/{slug}, /apps/{slug}, etc.
  const effectiveSelectedPath = useMemo(() => {
    const raw = sidebarRevealPath ?? location.pathname;
    const match = raw.match(/^\/(exchanges|dashboard|apps|runtime)\/([^/]+)(\/.*)?$/);
    if (match) return `/exchanges/${match[2]}${match[3] ?? ''}`;
    return raw;
  }, [sidebarRevealPath, location.pathname]);

  // --- About Me dialog -----------------------------------------------
  const [aboutMeOpen, setAboutMeOpen] = useState(false);
  const userMenuItems: DropdownItem[] = useMemo(() => [
    { label: 'About Me', icon: createElement(User, { size: 14 }), onClick: () => setAboutMeOpen(true) },
  ], []);

  // --- Exchange full-text search via command palette -----------------
  const [paletteQuery, setPaletteQuery] = useState('');
  const debouncedQuery = useDebouncedValue(paletteQuery, 300);
  const { data: exchangeResults } = useSearchExecutions(
    {
      text: debouncedQuery || undefined,
      applicationId: scope.appId || undefined,
      routeId: scope.routeId || undefined,
      offset: 0,
      limit: 10,
    },
    false,
  );

  const catalogData = useMemo(
    () => buildSearchData(catalog, agents as any[], attributeKeys),
    [catalog, agents, attributeKeys],
  );

  const catalogRef = useRef(catalogData);
  if (catalogData !== catalogRef.current && JSON.stringify(catalogData) !== JSON.stringify(catalogRef.current)) {
    catalogRef.current = catalogData;
  }

  const adminSearchData: SearchResult[] = useMemo(
    () => buildAdminSearchData(adminUsers, adminGroups, adminRoles),
    [adminUsers, adminGroups, adminRoles],
  );

  const alertingSearchData: SearchResult[] = useMemo(
    () => buildAlertSearchData(cmdkAlerts, cmdkRules),
    [cmdkAlerts, cmdkRules],
  );

  const operationalSearchData: SearchResult[] = useMemo(() => {
    if (isAdminPage) return [];

    const exchangeItems: SearchResult[] = (exchangeResults?.data || []).map((e: any) => ({
      id: e.executionId,
      category: 'exchange' as const,
      title: `...${e.executionId.slice(-8)}`,
      badges: [{ label: e.status, color: statusToColor(e.status) }],
      meta: `${e.routeId} · ${e.applicationId ?? ''} · ${formatDuration(e.durationMs)}`,
      path: `/exchanges/${e.applicationId ?? ''}/${e.routeId}/${e.executionId}`,
      serverFiltered: true,
      matchContext: e.highlight ?? undefined,
    }));

    const attributeItems: SearchResult[] = [];
    if (debouncedQuery) {
      const q = debouncedQuery.toLowerCase();
      for (const e of exchangeResults?.data || []) {
        if (!e.attributes) continue;
        for (const [key, value] of Object.entries(e.attributes as Record<string, string>)) {
          if (key.toLowerCase().includes(q) || String(value).toLowerCase().includes(q)) {
            attributeItems.push({
              id: `${e.executionId}-attr-${key}`,
              category: 'attribute' as const,
              title: `${key} = "${value}"`,
              badges: [{ label: e.status, color: statusToColor(e.status) }],
              meta: `${e.executionId} · ${e.routeId} · ${e.applicationId ?? ''}`,
              path: `/exchanges/${e.applicationId ?? ''}/${e.routeId}/${e.executionId}`,
              serverFiltered: true,
            });
          }
        }
      }
    }

    const facet = parseFacetQuery(debouncedQuery ?? '');
    const facetItems: SearchResult[] =
      facet
        ? [{
            id: `facet-${formatAttrParam(facet)}`,
            category: 'attribute' as const,
            title: `Filter: ${facet.key} = "${facet.value}"${facet.value?.includes('*') ? '  (wildcard)' : ''}`,
            meta: 'apply attribute filter',
            path: `/exchanges?attr=${encodeURIComponent(formatAttrParam(facet))}`,
          }]
        : [];

    return [...facetItems, ...catalogRef.current, ...exchangeItems, ...attributeItems, ...alertingSearchData];
  }, [isAdminPage, catalogRef.current, exchangeResults, debouncedQuery, alertingSearchData]);

  const searchData = isAdminPage ? adminSearchData : operationalSearchData;

  // --- Breadcrumb ---------------------------------------------------
  const breadcrumb = useMemo(() => {
    if (isAdminPage) {
      const LABELS: Record<string, string> = {
        admin: 'Admin',
        rbac: 'Users & Roles',
        audit: 'Audit Log',
        oidc: 'OIDC',
        database: 'Database',
        clickhouse: 'ClickHouse',
        'server-metrics': 'Server Metrics',
        appconfig: 'App Config',
      };
      const parts = location.pathname.split('/').filter(Boolean);
      return parts.map((part, i) => ({
        label: LABELS[part] ?? part,
        ...(i < parts.length - 1 ? { href: '/' + parts.slice(0, i + 1).join('/') } : {}),
      }));
    }
    const items: { label: string; href?: string }[] = [
      { label: 'All Applications', href: `/${scope.tab}` },
    ];
    if (scope.appId) {
      items.push({ label: scope.appId, href: `/${scope.tab}/${scope.appId}` });
    }
    if (scope.routeId) {
      items.push({ label: scope.routeId });
    }
    if (items.length > 0 && !scope.routeId && !scope.appId) {
      delete items[items.length - 1].href;
    }
    return items;
  }, [location.pathname, isAdminPage, scope.tab, scope.appId, scope.routeId]);

  // --- Callbacks ----------------------------------------------------
  const handleLogout = useCallback(() => {
    logout();
    navigate('/login');
  }, [logout, navigate]);

  const ADMIN_CATEGORIES = new Set(['user', 'group', 'role']);
  const ADMIN_TAB_MAP: Record<string, string> = { user: 'users', group: 'groups', role: 'roles' };

  const handlePaletteSelect = useCallback((result: any) => {
    if (result.category === 'alert' || result.category === 'alertRule') {
      if (result.path) navigate(result.path);
      setPaletteOpen(false);
      return;
    }

    if (result.category === 'attribute') {
      // Three sources feed 'attribute' results:
      //   - buildSearchData → id `attr-key-<key>` (key-only)
      //   - operationalSearchData per-exchange → id `<execId>-attr-<key>`, title `key = "value"`
      //   - synthetic facet (Task 9) → id `facet-<serialized>` where <serialized> is already
      //     the URL `attr=` form (`key` or `key:value`)
      let attrParam: string | null = null;
      if (typeof result.id === 'string' && result.id.startsWith('attr-key-')) {
        attrParam = result.id.substring('attr-key-'.length);
      } else if (typeof result.id === 'string' && result.id.startsWith('facet-')) {
        attrParam = result.id.substring('facet-'.length);
      } else if (typeof result.title === 'string') {
        const m = /^([a-zA-Z0-9._-]+)\s*=\s*"([^"]*)"/.exec(result.title);
        if (m) attrParam = `${m[1]}:${m[2]}`;
      }
      if (attrParam) {
        const base = ['/exchanges'];
        if (scope.appId) base.push(scope.appId);
        if (scope.routeId) base.push(scope.routeId);
        navigate(`${base.join('/')}?attr=${encodeURIComponent(attrParam)}`);
      }
      setPaletteOpen(false);
      return;
    }

    if (result.path) {
      if (ADMIN_CATEGORIES.has(result.category)) {
        const itemId = result.id.split(':').slice(1).join(':');
        navigate(result.path, {
          state: { tab: ADMIN_TAB_MAP[result.category], highlight: itemId },
        });
      } else {
        const state: Record<string, unknown> = { sidebarReveal: result.path };
        if (result.category === 'exchange') {
          const parts = result.path.split('/').filter(Boolean);
          if (parts.length === 4 && parts[0] === 'exchanges') {
            state.selectedExchange = {
              executionId: parts[3],
              applicationId: parts[1],
              routeId: parts[2],
            };
          }
        }
        navigate(result.path, { state });
      }
    }
    setPaletteOpen(false);
  }, [navigate, setPaletteOpen, scope.appId, scope.routeId]);

  const handlePaletteSubmit = useCallback((query: string) => {
    if (isAdminPage) {
      // Find first matching admin result and navigate to it
      const q = query.toLowerCase();
      const match = adminSearchData.find(
        (r) => r.title.toLowerCase().includes(q) || r.meta.toLowerCase().includes(q),
      );
      if (match) {
        handlePaletteSelect(match);
      } else {
        navigate('/admin/rbac');
      }
      return;
    }

    const facet = parseFacetQuery(query);
    const baseParts = ['/exchanges'];
    if (scope.appId) baseParts.push(scope.appId);
    if (scope.routeId) baseParts.push(scope.routeId);
    if (facet) {
      navigate(`${baseParts.join('/')}?attr=${encodeURIComponent(formatAttrParam(facet))}`);
      return;
    }
    navigate(`${baseParts.join('/')}?text=${encodeURIComponent(query)}`);
  }, [isAdminPage, adminSearchData, handlePaletteSelect, navigate, scope.appId, scope.routeId]);

  const handleSidebarNavigate = useCallback((path: string) => {
    const state = { sidebarReveal: path };

    // When not auto-refreshing, treat navigation as a manual refresh
    if (!autoRefresh) {
      refreshTimeRange();
      queryClient.invalidateQueries();
    }

    const appMatch = path.match(/^\/apps\/([^/]+)(?:\/(.+))?$/);
    if (appMatch) {
      const [, sAppId, sRouteId] = appMatch;
      if (scope.tab === 'apps') {
        navigate(`/apps/${sAppId}`, { state });
      } else if (scope.tab === 'runtime') {
        // Runtime tab has no route-level view — stay at app level
        navigate(`/runtime/${sAppId}`, { state });
      } else {
        navigate(sRouteId ? `/${scope.tab}/${sAppId}/${sRouteId}` : `/${scope.tab}/${sAppId}`, { state });
      }
      return;
    }

    const exchangeMatch = path.match(/^\/exchanges\/([^/]+)(?:\/(.+))?$/);
    if (exchangeMatch) {
      const [, sAppId, sRouteId] = exchangeMatch;
      if (scope.tab === 'apps') {
        navigate(`/apps/${sAppId}`, { state });
      } else if (scope.tab === 'runtime') {
        navigate(`/runtime/${sAppId}`, { state });
      } else {
        navigate(sRouteId ? `/${scope.tab}/${sAppId}/${sRouteId}` : `/${scope.tab}/${sAppId}`, { state });
      }
      return;
    }

    const agentMatch = path.match(/^\/agents\/([^/]+)(?:\/(.+))?$/);
    if (agentMatch) {
      const [, sAppId, sInstanceId] = agentMatch;
      navigate(sInstanceId ? `/runtime/${sAppId}/${sInstanceId}` : `/runtime/${sAppId}`, { state });
      return;
    }

    navigate(path, { state });
  }, [navigate, scope.tab, autoRefresh, refreshTimeRange, queryClient]);

  // --- Render -------------------------------------------------------
  const camelLogo = (
    <img src={sidebarLogo} alt="" style={{ width: 28, height: 28 }} />
  );

  const sidebarElement = (
    <Sidebar
      collapsed={sidebarCollapsed}
      onCollapseToggle={handleCollapseToggle}
      searchValue={filterQuery}
      onSearchChange={setFilterQuery}
    >
      <Sidebar.Header
        logo={camelLogo}
        title="Cameleer"
        version={__APP_VERSION__}
      />

      {/* Sidebar filters */}
      {!sidebarCollapsed && <div className={css.sidebarFilters}>
        <button
          className={`${css.filterChip} ${hideEmptyRoutes ? css.filterChipActive : ''}`}
          onClick={toggleHideEmptyRoutes}
          title="Hide routes with 0 executions"
        >
          <span className={css.filterChipIcon}><EyeOff size={10} /></span>
          Empty routes
        </button>
        <button
          className={`${css.filterChip} ${hideOfflineApps ? css.filterChipActive : ''}`}
          onClick={toggleHideOfflineApps}
          title="Hide stale and disconnected apps"
        >
          <span className={css.filterChipIcon}><EyeOff size={10} /></span>
          Offline apps
        </button>
      </div>}

      {/* Applications section */}
      <div className={css.appSectionWrap}>
        {canControl && !sidebarCollapsed && (
          <button
            className={css.addAppBtn}
            onClick={(e) => { e.stopPropagation(); navigate('/apps/new'); }}
            title="Create App"
          >
            <Plus size={12} /> App
          </button>
        )}
        <Sidebar.Section
          icon={createElement(Box, { size: 16 })}
          label="Applications"
          open={appsOpen}
          onToggle={toggleApps}
          maxHeight="50vh"
        >
          <SidebarTree
            nodes={appTreeNodes}
            selectedPath={effectiveSelectedPath}
            isStarred={isStarred}
            onToggleStar={toggleStar}
            filterQuery={filterQuery}
            persistKey="apps"
            autoRevealPath={sidebarRevealPath}
            onNavigate={handleSidebarNavigate}
          />
        </Sidebar.Section>
      </div>

      {/* Alerts section */}
      <Sidebar.Section
        icon={createElement(Bell, { size: 16 })}
        label="Alerts"
        open={alertsOpen}
        onToggle={toggleAlerts}
        active={isAlertsPage}
      >
        <SidebarTree
          nodes={alertsTreeNodes}
          selectedPath={location.pathname}
          isStarred={isStarred}
          onToggleStar={toggleStar}
          filterQuery={filterQuery}
          persistKey="alerts"
          autoRevealPath={sidebarRevealPath}
          onNavigate={handleSidebarNavigate}
        />
      </Sidebar.Section>

      {/* Starred section — only when there are starred items */}
      {starredItems.length > 0 && (
        <Sidebar.Section
          icon={createElement(Star, { size: 16 })}
          label="Starred"
          open={starredOpen}
          onToggle={() => setStarredOpen((v) => !v)}
          maxHeight="30vh"
        >
          <StarredList
            items={starredItems}
            onNavigate={handleSidebarNavigate}
            onRemove={toggleStar}
          />
        </Sidebar.Section>
      )}

      {/* Admin section — only visible to ADMIN role */}
      {isAdmin && (
        <Sidebar.Section
          icon={createElement(Settings, { size: 16 })}
          label="Admin"
          open={adminOpen}
          onToggle={toggleAdmin}
          active={isAdminPage}
          position="bottom"
        >
          <SidebarTree
            nodes={adminTreeNodes}
            selectedPath={location.pathname}
            isStarred={isStarred}
            onToggleStar={toggleStar}
            filterQuery={filterQuery}
            persistKey="admin"
            autoRevealPath={sidebarRevealPath}
            onNavigate={handleSidebarNavigate}
          />
        </Sidebar.Section>
      )}

      {/* Footer */}
      <Sidebar.Footer>
        <Sidebar.FooterLink
          icon={createElement(FileText, { size: 16 })}
          label="API Docs"
          active={location.pathname === '/api-docs'}
          onClick={() => handleSidebarNavigate('/api-docs')}
        />
      </Sidebar.Footer>
    </Sidebar>
  );

  return (
    <AppShell sidebar={sidebarElement}>
      <div
        aria-hidden
        style={{
          position: 'fixed',
          top: 0,
          left: 0,
          right: 0,
          height: 3,
          background: envBarColor,
          zIndex: 900,
          pointerEvents: 'none',
        }}
      />
      <EnvironmentSwitcherModal
        open={switcherOpen}
        onClose={() => setSwitcherOpen(false)}
        envs={envRecords}
        value={selectedEnv}
        onPick={(slug) => {
          setSelectedEnv(slug);
          setSwitcherOpen(false);
        }}
        forced={switcherForced}
      />
      <TopBar
        breadcrumb={breadcrumb}
        environment={
          <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            <EnvironmentSwitcherButton
              envs={envRecords}
              value={selectedEnv}
              onClick={() => setSwitcherOpen(true)}
            />
            <NotificationBell />
          </div>
        }
        user={{ name: username || 'Account' }}
        userMenuItems={userMenuItems}
        onLogout={handleLogout}
        onNavigate={navigate}
      >
        <SearchTrigger onClick={() => setPaletteOpen(true)} />
        <ButtonGroup
          items={STATUS_ITEMS}
          value={globalFilters.statusFilters}
          onChange={(sel) => {
            type S = 'completed' | 'warning' | 'failed' | 'running'
            const selected = sel as Set<S>
            const current = globalFilters.statusFilters
            for (const v of selected) {
              if (!current.has(v)) globalFilters.toggleStatus(v)
            }
            for (const v of current) {
              if (!selected.has(v)) globalFilters.toggleStatus(v)
            }
          }}
        />
        <TimeRangeDropdown
          value={globalFilters.timeRange}
          onChange={globalFilters.setTimeRange}
        />
        <AutoRefreshToggle
          active={globalFilters.autoRefresh}
          onChange={globalFilters.setAutoRefresh}
        />
      </TopBar>
      <AboutMeDialog open={aboutMeOpen} onClose={() => setAboutMeOpen(false)} />
      <CommandPalette
        key={isAdminPage ? 'admin' : 'ops'}
        open={paletteOpen}
        onClose={() => setPaletteOpen(false)}
        onOpen={() => setPaletteOpen(true)}
        onSelect={handlePaletteSelect}
        onSubmit={handlePaletteSubmit}
        onQueryChange={setPaletteQuery}
        data={searchData}
      />

      {!isAdminPage && !isAlertsPage && (
        <ContentTabs active={scope.tab} onChange={setTab} scope={scope} />
      )}

      <main className={css.mainContent}>
        <Outlet key={selectedEnv ?? '__all__'} />
      </main>
    </AppShell>
  );
}

export function LayoutShell() {
  return (
    <ToastProvider>
      <CommandPaletteProvider>
        <GlobalFilterProvider>
          <BreadcrumbProvider>
            <LayoutContent />
          </BreadcrumbProvider>
        </GlobalFilterProvider>
      </CommandPaletteProvider>
    </ToastProvider>
  );
}
