import { useCallback, useEffect, useMemo, useState } from 'react';
import { Check, ChevronDown, Cpu, Star } from 'lucide-react';
import { toast } from 'sonner';
import { api } from '@/api/client';
import type { ModelCatalogProvider, ModelInfo } from '@/api/types';
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { BottomSheet } from '@/components/BottomSheet';
import { useViewport } from '@/hooks/useViewport';
import { formatModelLabel } from '@/lib/model-label';

interface Props {
  value: string | null;
  onChange: (model: string) => void | Promise<void>;
}

interface PickerState {
  providers: ModelCatalogProvider[];
  favoriteModels: string[];
  /** P6.1: compositeId -> advisory badge kinds (from BooControl). */
  badges: Record<string, string[]>;
  /** P6.1: badge kind -> human label. */
  badgeLabels: Record<string, string>;
  /** compositeIds of models currently loaded (from llama-swap /running). */
  loadedModels: Set<string>;
  error: string | null;
}

const FAVORITE_MODELS_KEY = 'favorite_models';

/** Short chip text per advisory badge kind. */
const BADGE_SHORT: Record<string, string> = {
  'best-code': 'code',
  'best-chat': 'chat',
  'best-fast': 'fast',
};

// P6.1: advisory routing scores from BooControl. Non-fatal — the control
// service may be down, in which case the picker simply shows no badges.
async function fetchRoutingBadges(): Promise<{ badges: Record<string, string[]>; badgeLabels: Record<string, string> }> {
  try {
    const res = await fetch('/api/control/routing/scores');
    if (!res.ok) return { badges: {}, badgeLabels: {} };
    const data = await res.json() as { badges?: Record<string, string[]>; badgeLabels?: Record<string, string> };
    return { badges: data.badges ?? {}, badgeLabels: data.badgeLabels ?? {} };
  } catch {
    return { badges: {}, badgeLabels: {} };
  }
}

async function fetchProviderStatus(): Promise<Set<string>> {
  try {
    const res = await fetch('/api/providers/status');
    if (!res.ok) return new Set();
    const data = await res.json() as {
      providers?: Array<{ running?: Array<{ compositeId: string }> }>;
    };
    const ids = new Set<string>();
    for (const p of data.providers ?? []) {
      for (const m of p.running ?? []) {
        if (m.compositeId) ids.add(m.compositeId);
      }
    }
    return ids;
  } catch {
    return new Set();
  }
}

async function fetchPickerData(): Promise<PickerState> {
  const [catalog, settings, routing, loadedModels] = await Promise.all([
    api.models(),
    api.settings.get(),
    fetchRoutingBadges(),
    fetchProviderStatus(),
  ]);
  const raw = settings[FAVORITE_MODELS_KEY];
  const favoriteModels = Array.isArray(raw)
    ? raw.filter((m): m is string => typeof m === 'string')
    : [];
  return {
    providers: catalog.providers,
    favoriteModels,
    badges: routing.badges,
    badgeLabels: routing.badgeLabels,
    loadedModels,
    error: null,
  };
}

// P7.3: detect an orphaned auto:* session — the selected model looks like a
// gateway virtual model but no provider in the live catalog serves it (the
// gateway registry entry was removed). The session keeps its id; we flag it.
function isOrphanedGatewayValue(value: string | null, providers: ModelCatalogProvider[]): boolean {
  if (!value) return false;
  const tail = value.includes('/') ? value.slice(value.indexOf('/') + 1) : value;
  const looksGateway = tail === 'auto' || tail.startsWith('auto:');
  if (!looksGateway) return false;
  const present = providers.some((p) => p.models.some((m) => m.id === value));
  return !present;
}

function ModelBadges({ ids, labels }: { ids: string[] | undefined; labels: Record<string, string> }) {
  if (!ids || ids.length === 0) return null;
  return (
    <span className="flex items-center gap-1 shrink-0">
      {ids.map((kind) => (
        <span
          key={kind}
          title={labels[kind] ?? kind}
          className="px-1 py-px text-[10px] leading-none rounded bg-emerald-500/15 text-emerald-400 border border-emerald-500/30"
        >
          {BADGE_SHORT[kind] ?? kind}
        </span>
      ))}
    </span>
  );
}

function ModelRow({
  id,
  isSelected,
  isFavorite,
  isLoaded,
  badges,
  badgeLabels,
  onPick,
  onToggleFavorite,
}: {
  id: string;
  isSelected: boolean;
  isFavorite: boolean;
  isLoaded?: boolean;
  badges?: string[];
  badgeLabels: Record<string, string>;
  onPick: (id: string) => void;
  onToggleFavorite: (id: string, favorite: boolean) => void;
}) {
  return (
    <div className="flex items-center gap-1 group">
      <button
        type="button"
        onClick={(e) => {
          e.stopPropagation();
          onToggleFavorite(id, !isFavorite);
        }}
        className="shrink-0 flex items-center justify-center size-5 rounded hover:bg-muted text-muted-foreground hover:text-foreground"
        aria-label={isFavorite ? 'Remove from favorites' : 'Add to favorites'}
      >
        <Star
          className={`size-3 ${isFavorite ? 'fill-yellow-400 text-yellow-400' : 'opacity-0 group-hover:opacity-60'}`}
        />
      </button>
      <button
        type="button"
        onClick={() => onPick(id)}
        className="flex-1 text-left flex items-center gap-2 font-mono text-xs py-1 rounded hover:bg-accent"
      >
        <Check className={`size-3 shrink-0 ${isSelected ? 'opacity-100' : 'opacity-0'}`} />
        <span className="truncate">{formatModelLabel(id)}</span>
        {isLoaded && (
          <span className="px-1 py-px text-[10px] leading-none rounded bg-green-500/15 text-green-400 border border-green-500/30 shrink-0">
            loaded
          </span>
        )}
        <ModelBadges ids={badges} labels={badgeLabels} />
      </button>
    </div>
  );
}

function ModelSections({
  providers,
  favoriteModels,
  selectedModel,
  loadedModels,
  badges,
  badgeLabels,
  onPick,
  onToggleFavorite,
}: {
  providers: ModelCatalogProvider[];
  favoriteModels: string[];
  selectedModel: string | null;
  loadedModels: Set<string>;
  badges: Record<string, string[]>;
  badgeLabels: Record<string, string>;
  onPick: (id: string) => void;
  onToggleFavorite: (id: string, favorite: boolean) => void;
}) {
  const favSet = useMemo(() => new Set(favoriteModels), [favoriteModels]);

  const modelMap = useMemo(() => {
    const map = new Map<string, ModelInfo>();
    for (const p of providers) {
      for (const m of p.models) {
        map.set(m.id, m);
      }
    }
    return map;
  }, [providers]);

  // Favorites section: only models that exist in the live inventory.
  const favoriteModelsInInventory = useMemo(
    () => favoriteModels.filter((id) => modelMap.has(id)),
    [favoriteModels, modelMap],
  );

  // For the non-dropdown (mobile bottom sheet) view, wrap each section.
  // The dropdown version uses the primitives directly.
  return (
    <>
          {favoriteModelsInInventory.length > 0 && (
        <>
          <DropdownMenuLabel>Favorites</DropdownMenuLabel>
          {favoriteModelsInInventory.map((id) => (
            <DropdownMenuItem
              key={id}
              onSelect={(e) => {
                e.preventDefault();
              }}
              className="flex items-center gap-1 p-0"
            >
              <ModelRow
                id={id}
                isSelected={selectedModel === id}
                isFavorite={favSet.has(id)}
                isLoaded={loadedModels.has(id)}
                badges={badges[id]}
                badgeLabels={badgeLabels}
                onPick={onPick}
                onToggleFavorite={onToggleFavorite}
              />
            </DropdownMenuItem>
          ))}
          <DropdownMenuSeparator />
        </>
      )}

      {providers.map((provider) => {
        if (provider.models.length === 0) return null;
        return (
          <div key={provider.id}>
            <DropdownMenuLabel>{provider.label}</DropdownMenuLabel>
            {provider.models.map((m) => (
              <DropdownMenuItem
                key={m.id}
                onSelect={(e) => {
                  e.preventDefault();
                }}
                className="flex items-center gap-1 p-0"
              >
                <ModelRow
                  id={m.id}
                  isSelected={selectedModel === m.id}
                  isFavorite={favSet.has(m.id)}
                  isLoaded={loadedModels.has(m.id)}
                  badges={badges[m.id]}
                  badgeLabels={badgeLabels}
                  onPick={onPick}
                  onToggleFavorite={onToggleFavorite}
                />
              </DropdownMenuItem>
            ))}
            <DropdownMenuSeparator />
          </div>
        );
      })}
    </>
  );
}

// Mobile bottom-sheet version of the grouped model list.
function MobileModelList({
  providers,
  favoriteModels,
  selectedModel,
  loadedModels,
  badges,
  badgeLabels,
  onPick,
  onToggleFavorite,
}: {
  providers: ModelCatalogProvider[];
  favoriteModels: string[];
  selectedModel: string | null;
  loadedModels: Set<string>;
  badges: Record<string, string[]>;
  badgeLabels: Record<string, string>;
  onPick: (id: string) => void;
  onToggleFavorite: (id: string, favorite: boolean) => void;
}) {
  const favSet = useMemo(() => new Set(favoriteModels), [favoriteModels]);

  const modelMap = useMemo(() => {
    const map = new Map<string, ModelInfo>();
    for (const p of providers) {
      for (const m of p.models) {
        map.set(m.id, m);
      }
    }
    return map;
  }, [providers]);

  const favoriteModelsInInventory = useMemo(
    () => favoriteModels.filter((id) => modelMap.has(id)),
    [favoriteModels, modelMap],
  );

  return (
    <div className="space-y-1">
      {favoriteModelsInInventory.length > 0 && (
        <div>
          <div className="text-xs font-medium text-muted-foreground px-2 py-1">Favorites</div>
          {favoriteModelsInInventory.map((id) => (
            <ModelRow
              key={id}
              id={id}
              isSelected={selectedModel === id}
              isFavorite={favSet.has(id)}
              isLoaded={loadedModels.has(id)}
              badges={badges[id]}
              badgeLabels={badgeLabels}
              onPick={onPick}
              onToggleFavorite={onToggleFavorite}
            />
          ))}
          <div className="h-px bg-border mx-2 my-1" />
        </div>
      )}

      {providers.map((provider) => {
        if (provider.models.length === 0) return null;
        return (
          <div key={provider.id}>
            <div className="text-xs font-medium text-muted-foreground px-2 py-1">{provider.label}</div>
            {provider.models.map((m) => (
              <ModelRow
                key={m.id}
                id={m.id}
                isSelected={selectedModel === m.id}
                isFavorite={favSet.has(m.id)}
                isLoaded={loadedModels.has(m.id)}
                badges={badges[m.id]}
                badgeLabels={badgeLabels}
                onPick={onPick}
                onToggleFavorite={onToggleFavorite}
              />
            ))}
            <div className="h-px bg-border mx-2 my-1" />
          </div>
        );
      })}
    </div>
  );
}

export function ModelPicker({ value, onChange }: Props) {
  const { isMobile } = useViewport();
  const [state, setState] = useState<PickerState | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [open, setOpen] = useState(false);
  // D2: live "currently routes to X" for the selected auto:* model.
  const [routesTo, setRoutesTo] = useState<string | null>(null);
  useEffect(() => {
    if (!open || state !== null) return;
    fetchPickerData()
      .then(setState)
      .catch((err) =>
        setError(err instanceof Error ? err.message : 'failed to load models'),
      );
  }, [open, state]);

  // D2: when an auto:* model is selected, show where the gateway last routed it.
  useEffect(() => {
    if (!open || !value) { setRoutesTo(null); return; }
    const tail = value.includes('/') ? value.slice(value.indexOf('/') + 1) : value;
    if (tail !== 'auto' && !tail.startsWith('auto:')) { setRoutesTo(null); return; }
    let alive = true;
    fetch(`/api/control/policies/dispatch-log?virtualModel=${encodeURIComponent(tail)}`)
      .then((r) => (r.ok ? r.json() : null))
      .then((d: { dispatches?: Array<{ chosenProviderId: string | null; chosenModel: string | null; status: string }> } | null) => {
        if (!alive) return;
        const last = d?.dispatches?.find((x) => x.chosenProviderId);
        setRoutesTo(last ? `${last.chosenProviderId}/${last.chosenModel}` : null);
      })
      .catch(() => { if (alive) setRoutesTo(null); });
    return () => { alive = false; };
  }, [open, value]);

  // Reset state when dropdown closes so we re-fetch fresh data next open.
  const handleOpenChange = useCallback((v: boolean) => {
    setOpen(v);
    if (!v) {
      setState(null);
      setError(null);
    }
  }, []);

  const toggleFavorite = useCallback(
    async (id: string, favorite: boolean) => {
      const current = state?.favoriteModels ?? [];
      const next = favorite
        ? [...current, id]
        : current.filter((m) => m !== id);
      try {
        const settings = await api.settings.patch({
          [FAVORITE_MODELS_KEY]: next,
        });
        const raw = settings[FAVORITE_MODELS_KEY];
        const normalized = Array.isArray(raw)
          ? raw.filter((m): m is string => typeof m === 'string')
          : [];
        setState((prev) =>
          prev ? { ...prev, favoriteModels: normalized } : prev,
        );
      } catch (err) {
        toast.error(
          err instanceof Error ? err.message : 'Failed to update favorites',
        );
      }
    },
    [state],
  );

  function handlePick(id: string) {
    setOpen(false);
    void onChange(id);
  }

  if (isMobile) {
    return (
      <>
        <button
          type="button"
          onClick={() => setOpen(true)}
          aria-label={`Model: ${value ?? 'default'}`}
          title={value ?? undefined}
          className="inline-flex items-center justify-center min-h-[36px] min-w-[44px] rounded text-muted-foreground hover:text-foreground"
        >
          <Cpu className="size-4" />
        </button>
        <BottomSheet open={open} onClose={() => handleOpenChange(false)} title="Model">
          <div className="px-2 py-2">
            {error && (
              <div className="px-2 py-1.5 text-xs text-destructive">{error}</div>
            )}
            {state === null && !error && (
              <div className="px-2 py-1.5 text-xs text-muted-foreground">Loading…</div>
            )}
            {state && isOrphanedGatewayValue(value, state.providers) && (
              <div className="px-2 py-1.5 mb-1 text-xs text-amber-400 bg-amber-500/10 border border-amber-500/30 rounded">
                Routing gateway offline — this session's <span className="font-mono">{value}</span> model can't route. Pick a concrete model.
              </div>
            )}
            {state && routesTo && (
              <div className="px-2 py-1 mb-1 text-[11px] text-emerald-400">
                Auto routes to <span className="font-mono">{routesTo}</span> (last dispatch)
              </div>
            )}
            {state && (
              <MobileModelList
                providers={state.providers}
                favoriteModels={state.favoriteModels}
                selectedModel={value}
                loadedModels={state.loadedModels}
                badges={state.badges}
                badgeLabels={state.badgeLabels}
                onPick={handlePick}
                onToggleFavorite={toggleFavorite}
              />
            )}
          </div>
        </BottomSheet>
      </>
    );
  }

  return (
    <DropdownMenu open={open} onOpenChange={handleOpenChange}>
      <DropdownMenuTrigger asChild>
        <button
          type="button"
          className="text-xs font-mono text-muted-foreground hover:text-foreground flex items-center gap-1 px-1.5 py-0.5 rounded hover:bg-muted/60"
        >
          {value ? formatModelLabel(value) : 'Model'}
          <ChevronDown className="size-3 opacity-70" />
        </button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end" className="max-h-72 min-w-[18rem] overflow-y-auto">
        {error && (
          <div className="px-2 py-1.5 text-xs text-destructive">{error}</div>
        )}
        {state === null && !error && (
          <div className="px-2 py-1.5 text-xs text-muted-foreground">Loading…</div>
        )}
        {state && isOrphanedGatewayValue(value, state.providers) && (
          <div className="px-2 py-1.5 mb-1 text-xs text-amber-400 bg-amber-500/10 border border-amber-500/30 rounded">
            Routing gateway offline — this session's <span className="font-mono">{value}</span> model can't route. Pick a concrete model.
          </div>
        )}
        {state && routesTo && (
          <div className="px-2 py-1 mb-1 text-[11px] text-emerald-400">
            Auto routes to <span className="font-mono">{routesTo}</span> (last dispatch)
          </div>
        )}
        {state && (
          <ModelSections
            providers={state.providers}
            favoriteModels={state.favoriteModels}
            selectedModel={value}
            loadedModels={state.loadedModels}
            badges={state.badges}
            badgeLabels={state.badgeLabels}
            onPick={handlePick}
            onToggleFavorite={toggleFavorite}
          />
        )}
      </DropdownMenuContent>
    </DropdownMenu>
  );
}
