import { Outlet, useNavigate, useLocation } from 'react-router';
import { AppShell, Sidebar, TopBar, CommandPalette, CommandPaletteProvider, GlobalFilterProvider, ToastProvider, BreadcrumbProvider, useCommandPalette, useGlobalFilters } from '@cameleer/design-system';
import type { SidebarApp, SearchResult } from '@cameleer/design-system';
import { useRouteCatalog } from '../api/queries/catalog';
import { useAgents } from '../api/queries/agents';
import { useSearchExecutions, useAttributeKeys } from '../api/queries/executions';
import { useAuthStore } from '../auth/auth-store';
import { useState, useMemo, useCallback, useEffect, useRef } from 'react';
import { ContentTabs } from './ContentTabs';
import { useScope } from '../hooks/useScope';

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

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

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

    for (const route of (app.routes || [])) {
      results.push({
        id: `${app.appId}/${route.routeId}`,
        category: 'route',
        title: route.routeId,
        badges: [{ label: app.appId }],
        meta: `${(route.exchangeCount ?? 0).toLocaleString()} exchanges`,
        path: `/exchanges/${app.appId}/${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: healthToColor((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',
      });
    }
  }

  return results;
}

function formatDuration(ms: number): string {
  if (ms >= 60_000) return `${(ms / 1000).toFixed(0)}s`;
  if (ms >= 1000) return `${(ms / 1000).toFixed(2)}s`;
  return `${ms}ms`;
}

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;
}

function LayoutContent() {
  const navigate = useNavigate();
  const location = useLocation();
  const { timeRange } = useGlobalFilters();
  const { data: catalog } = useRouteCatalog(timeRange.start.toISOString(), timeRange.end.toISOString());
  const { data: agents } = useAgents();
  const { data: attributeKeys } = useAttributeKeys();
  const { username, logout } = useAuthStore();
  const { open: paletteOpen, setOpen: setPaletteOpen } = useCommandPalette();
  const { scope, setTab } = useScope();

  // Exchange full-text search via command palette (scoped to current sidebar selection)
  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 sidebarApps: SidebarApp[] = useMemo(() => {
    if (!catalog) return [];
    const cmp = (a: string, b: string) => a.localeCompare(b);
    return [...catalog]
      .sort((a: any, b: any) => cmp(a.appId, b.appId))
      .map((app: any) => ({
        id: app.appId,
        name: app.appId,
        health: app.health as 'live' | 'stale' | 'dead',
        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,
          })),
        agents: [...(app.agents || [])]
          .sort((a: any, b: any) => cmp(a.name, b.name))
          .map((a: any) => ({
            id: a.id,
            name: a.name,
            status: a.status as 'live' | 'stale' | 'dead',
            tps: a.tps,
          })),
      }));
  }, [catalog]);

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

  // Stable reference for catalog data — only changes when catalog/agents actually change,
  // not on every poll cycle (prevents cmd-k scroll reset)
  const catalogRef = useRef(catalogData);
  if (catalogData !== catalogRef.current && JSON.stringify(catalogData) !== JSON.stringify(catalogRef.current)) {
    catalogRef.current = catalogData;
  }

  const searchData: SearchResult[] = useMemo(() => {
    const exchangeItems: SearchResult[] = (exchangeResults?.data || []).map((e: any) => ({
      id: e.executionId,
      category: 'exchange' as const,
      title: e.executionId,
      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,
            });
          }
        }
      }
    }

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

  const isAdminPage = location.pathname.startsWith('/admin');
  const breadcrumb = useMemo(() => {
    if (isAdminPage) {
      const LABELS: Record<string, string> = {
        admin: 'Admin',
        rbac: 'Users & Roles',
        audit: 'Audit Log',
        oidc: 'OIDC',
        database: 'Database',
        clickhouse: 'ClickHouse',
        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('/') } : {}),
      }));
    }
    // Scope trail as breadcrumb items
    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 });
    }
    // Last item has no href (current location)
    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]);

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

  const handlePaletteSelect = useCallback((result: any) => {
    if (result.path) {
      const state: Record<string, unknown> = { sidebarReveal: result.path };

      // For exchange/attribute results, pass selectedExchange in state
      if (result.category === 'exchange' || result.category === 'attribute') {
        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]);

  const handlePaletteSubmit = useCallback((query: string) => {
    const baseParts = ['/exchanges'];
    if (scope.appId) baseParts.push(scope.appId);
    if (scope.routeId) baseParts.push(scope.routeId);
    navigate(`${baseParts.join('/')}?text=${encodeURIComponent(query)}`);
  }, [navigate, scope.appId, scope.routeId]);

  // Translate Sidebar's internal paths to our URL structure.
  // Pass sidebarReveal state so the DS Sidebar can highlight the clicked entry.
  const handleSidebarNavigate = useCallback((path: string) => {
    const state = { sidebarReveal: path };

    // /apps/:appId and /apps/:appId/:routeId → current tab
    const appMatch = path.match(/^\/apps\/([^/]+)(?:\/(.+))?$/);
    if (appMatch) {
      const [, sAppId, sRouteId] = appMatch;
      navigate(sRouteId ? `/${scope.tab}/${sAppId}/${sRouteId}` : `/${scope.tab}/${sAppId}`, { state });
      return;
    }

    // /agents/:appId/:instanceId → runtime tab
    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]);

  return (
    <AppShell
      sidebar={
        <Sidebar apps={sidebarApps} onNavigate={handleSidebarNavigate} />
      }
    >
      <TopBar
        breadcrumb={breadcrumb}
        user={username ? { name: username } : undefined}
        onLogout={handleLogout}
      />
      <CommandPalette
        open={paletteOpen}
        onClose={() => setPaletteOpen(false)}
        onOpen={() => setPaletteOpen(true)}
        onSelect={handlePaletteSelect}
        onSubmit={handlePaletteSubmit}
        onQueryChange={setPaletteQuery}
        data={searchData}
      />

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

      <main style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0, padding: isAdminPage ? '1.5rem' : 0 }}>
        <Outlet />
      </main>
    </AppShell>
  );
}

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