import { useState, useEffect, useMemo, useCallback, useRef } from "react";
import { useNavigate } from "react-router";
import { Command } from "cmdk";
import {
  Search,
  Building2,
  FolderGit2,
  Hash,
  CircleDot,
  Loader2,
  AlertCircle,
  CornerDownLeft,
  ArrowUpDown,
  X,
} from "lucide-react";
import { api } from "@/client";

// ---- Types ----

type HitType = "workspace" | "repo" | "room" | "issue";

interface SearchHit {
  id: string;
  type: HitType;
  label: string;
  subtitle: string;
  url: string;
  meta?: string;
  metaTone?: "success" | "muted" | "warning";
}

interface SearchGroup<T> {
  items: T[];
  total: number;
  has_more: boolean;
}

interface SearchResponse {
  workspaces: SearchGroup<{ name: string; description?: string | null }>;
  repos: SearchGroup<{ name: string; workspace: string; description?: string | null }>;
  rooms: SearchGroup<{ id: string; name: string; workspace: string }>;
  issues: SearchGroup<{ number: number; title: string; state: string; workspace: string }>;
}

const TYPE_ICONS: Record<HitType, typeof Building2> = {
  workspace: Building2,
  repo: FolderGit2,
  room: Hash,
  issue: CircleDot,
};

const TYPE_LABELS: Record<HitType, string> = {
  workspace: "Workspaces",
  repo: "Repositories",
  room: "Channels",
  issue: "Issues",
};

const PLACEHOLDERS = [
  "Search workspaces, repos, channels...",
  "Find an issue...",
  "Jump to a channel...",
  "Open a repository...",
];

// ---- Helpers ----

function buildHits(data: SearchResponse): SearchHit[] {
  const results: SearchHit[] = [];

  for (const ws of data.workspaces.items) {
    results.push({
      id: `ws-${ws.name}`,
      type: "workspace",
      label: ws.name,
      subtitle: ws.description || "Workspace",
      url: `/${ws.name}`,
    });
  }

  for (const repo of data.repos.items) {
    results.push({
      id: `repo-${repo.workspace}-${repo.name}`,
      type: "repo",
      label: `${repo.workspace}/${repo.name}`,
      subtitle: repo.description || "Repository",
      url: `/${repo.workspace}/repo/${repo.name}`,
    });
  }

  for (const room of data.rooms.items) {
    results.push({
      id: `room-${room.id}`,
      type: "room",
      label: `#${room.name}`,
      subtitle: "Channel",
      url: `/${room.workspace}/channel/${room.id}`,
    });
  }

  for (const issue of data.issues.items) {
    results.push({
      id: `issue-${issue.workspace}-${issue.number}`,
      type: "issue",
      label: `#${issue.number} ${issue.title}`,
      subtitle: issue.workspace,
      meta: issue.state,
      metaTone: issue.state === "open" ? "success" : "muted",
      url: `/${issue.workspace}/issues/${issue.number}`,
    });
  }

  return results;
}

// ---- Component ----

export function CommandPalette() {
  const [open, setOpen] = useState(false);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [items, setItems] = useState<SearchHit[]>([]);
  const [search, setSearch] = useState("");
  const [placeholderIndex, setPlaceholderIndex] = useState(0);
  const navigate = useNavigate();
  const inputRef = useRef<HTMLInputElement>(null);

  // Rotating placeholder
  useEffect(() => {
    if (!open) return;
    const interval = setInterval(() => {
      setPlaceholderIndex((i) => (i + 1) % PLACEHOLDERS.length);
    }, 3000);
    return () => clearInterval(interval);
  }, [open]);

  // Fetch data
  const fetchData = useCallback(async (q: string) => {
    setLoading(true);
    setError(null);
    try {
      const { data } = await api.get<SearchResponse>("/api/v1/search", {
        params: q.trim() ? { q: q.trim() } : {},
      });
      setItems(buildHits(data));
    } catch (err) {
      setError(err instanceof Error ? err.message : "Search failed");
      setItems([]);
    }
    setLoading(false);
  }, []);

  // Pre-fetch on open, debounced re-fetch on type
  useEffect(() => {
    if (!open) return;
    const timer = setTimeout(() => fetchData(search), search ? 150 : 0);
    return () => clearTimeout(timer);
  }, [open, search, fetchData]);

  // Reset on close
  const handleOpenChange = useCallback((o: boolean) => {
    setOpen(o);
    if (!o) {
      setSearch("");
      setError(null);
    }
  }, []);

  // Group by type
  const grouped = useMemo(() => {
    const g: Record<string, SearchHit[]> = {};
    for (const it of items) {
      const key = TYPE_LABELS[it.type];
      (g[key] ??= []).push(it);
    }
    return g;
  }, [items]);

  const totalHits = items.length;

  // ⌘K global shortcut
  useEffect(() => {
    const down = (e: KeyboardEvent) => {
      if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
        e.preventDefault();
        setOpen((v) => !v);
      }
    };
    document.addEventListener("keydown", down);
    return () => document.removeEventListener("keydown", down);
  }, []);

  const handleSelect = useCallback(
    (url: string) => {
      setOpen(false);
      navigate(url);
    },
    [navigate],
  );

  return (
    <>
      {/* Trigger button */}
      <button
        aria-label="Open command palette (⌘K)"
        className="inline-flex items-center gap-2 rounded-lg border border-border/60 bg-muted/40 px-3 py-1.5 text-xs text-muted-foreground transition-all duration-150 hover:border-border hover:bg-muted hover:text-foreground hover:shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
        onClick={() => setOpen(true)}
        type="button"
      >
        <Search className="size-3.5" />
        <span className="hidden sm:inline">Quick jump...</span>
        <kbd className="ml-1 hidden rounded border border-border bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground sm:inline">
          ⌘K
        </kbd>
      </button>

      {/* Modal dialog */}
      <Command.Dialog
        label="Command Palette"
        onOpenChange={handleOpenChange}
        open={open}
      >
        {/* Search input */}
        <div className="flex items-center gap-3 border-b border-border px-4 py-0.5">
          <Search className="size-4 shrink-0 text-muted-foreground/60" />
          <Command.Input
            autoFocus
            className="flex-1 bg-transparent py-3 text-[15px] outline-none placeholder:text-muted-foreground/40"
            onValueChange={setSearch}
            placeholder={PLACEHOLDERS[placeholderIndex]}
            ref={inputRef}
            value={search}
          />
          <div className="flex items-center gap-1.5">
            {loading && (
              <Loader2 className="size-4 animate-spin text-muted-foreground/50" />
            )}
            {error && (
              <AlertCircle className="size-4 text-destructive/70" />
            )}
          </div>
        </div>

        {/* Results list */}
        <Command.List className="max-h-[420px] overflow-y-auto overscroll-contain p-1">
          {/* Error state */}
          {error && (
            <div className="flex flex-col items-center gap-2 px-4 py-10">
              <div className="grid size-10 place-items-center rounded-full bg-destructive/10">
                <AlertCircle className="size-5 text-destructive/70" />
              </div>
              <p className="text-sm text-muted-foreground">{error}</p>
              <button
                className="mt-1 rounded-md px-3 py-1 text-xs text-primary transition-colors hover:bg-primary/10"
                onClick={() => fetchData(search)}
                type="button"
              >
                Retry
              </button>
            </div>
          )}

          {/* Empty state */}
          {!error && !loading && totalHits === 0 && (
            <div className="flex flex-col items-center gap-2 px-4 py-10">
              <div className="grid size-10 place-items-center rounded-full bg-muted/50">
                <Search className="size-5 text-muted-foreground/30" />
              </div>
              <p className="text-sm text-muted-foreground">
                {search.trim()
                  ? `No results for "${search.trim()}"`
                  : "Type to search across all workspaces"}
              </p>
            </div>
          )}

          {/* Loading skeleton */}
          {loading && totalHits === 0 && !error && (
            <div className="space-y-3 px-4 py-4">
              {Array.from({ length: 4 }).map((_, i) => (
                <div className="flex items-center gap-3" key={i}>
                  <div className="size-4 animate-pulse rounded bg-muted/50" />
                  <div className="flex-1 space-y-1.5">
                    <div className="h-4 w-3/5 animate-pulse rounded bg-muted/50" />
                    <div className="h-3 w-2/5 animate-pulse rounded bg-muted/40" />
                  </div>
                </div>
              ))}
            </div>
          )}

          {/* Grouped results */}
          {Object.entries(grouped).map(([groupName, groupItems]) => (
            <Command.Group
              heading={
                <span className="flex items-center gap-2">
                  {groupName}
                  <span className="rounded bg-muted/50 px-1.5 py-0.5 text-[10px] tabular-nums text-muted-foreground/60">
                    {groupItems.length}
                  </span>
                </span>
              }
              key={groupName}
            >
              {groupItems.map((item) => {
                const Icon = TYPE_ICONS[item.type];
                return (
                  <Command.Item
                    aria-label={`${item.type}: ${item.label}`}
                    className="group mx-0 flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2.5 text-sm transition-colors aria-selected:bg-accent"
                    key={item.id}
                    onSelect={() => handleSelect(item.url)}
                    value={item.id}
                  >
                    <span
                      className={`grid size-8 shrink-0 place-items-center rounded-lg ${
                        item.type === "issue"
                          ? "bg-warning/10 text-warning dark:text-warning"
                          : item.type === "repo"
                            ? "bg-info/10 text-info dark:text-info"
                            : item.type === "room"
                              ? "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
                              : "bg-primary/10 text-primary"
                      }`}
                    >
                      <Icon className="size-4" />
                    </span>
                    <div className="min-w-0 flex-1">
                      <div className="truncate text-[14px] font-medium text-foreground">
                        {item.label}
                      </div>
                      <div className="flex items-center gap-1.5">
                        <span className="truncate text-xs text-muted-foreground/60">
                          {item.subtitle}
                        </span>
                        {item.meta && (
                          <span
                            className={`shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-medium ${
                              item.metaTone === "success"
                                ? "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
                                : "bg-muted/60 text-muted-foreground/60"
                            }`}
                          >
                            {item.meta}
                          </span>
                        )}
                      </div>
                    </div>
                    <kbd className="hidden shrink-0 rounded border border-border bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground/40 group-aria-selected:text-foreground/50">
                      ↵
                    </kbd>
                  </Command.Item>
                );
              })}
            </Command.Group>
          ))}
        </Command.List>

        {/* Footer */}
        <div className="flex items-center justify-between border-t border-border px-4 py-2 text-[11px] text-muted-foreground/40">
          <div className="flex items-center gap-3">
            <span className="inline-flex items-center gap-1">
              <ArrowUpDown className="size-3" />
              Navigate
            </span>
            <span className="inline-flex items-center gap-1">
              <CornerDownLeft className="size-3" />
              Open
            </span>
            <span className="inline-flex items-center gap-1">
              <X className="size-3" />
              Close
            </span>
          </div>
          {totalHits > 0 && (
            <span className="tabular-nums">
              {totalHits} result{totalHits !== 1 ? "s" : ""}
            </span>
          )}
        </div>
      </Command.Dialog>
    </>
  );
}
