// api.ts から分割（挙動不変）: ワーカー・バックエンド・ノード状態（Side Info Panel）。

export interface WorkerInfo {
  id: string;
  endpoint: string | null;
  model: string | null;
  roles: string[];
  enabled: boolean;
  /** True if this worker fronts an LLM gateway / proxy (Phase A). */
  proxy?: boolean;
  /** Proxy implementation; only 'litellm' is currently shipped. */
  proxyType?: 'litellm';
}

export async function fetchWorkers(): Promise<WorkerInfo[]> {
  const res = await fetch('/api/workers', { credentials: 'include' });
  if (!res.ok) return [];
  const data = await res.json() as { workers?: WorkerInfo[] };
  return data.workers ?? [];
}

export interface BackendInfo {
  id: string;
  model: string | null;
  online: boolean;
}

export interface WorkerBackendsResponse {
  source: 'direct' | 'proxy';
  proxyType?: 'litellm';
  backends: BackendInfo[];
  /** Set when the proxy probe failed (network error, 5xx). UI renders degraded. */
  error?: string;
}

export async function fetchWorkerBackends(workerId: string): Promise<WorkerBackendsResponse> {
  const res = await fetch(`/api/workers/${encodeURIComponent(workerId)}/backends`, {
    credentials: 'include',
  });
  if (!res.ok && res.status !== 502) {
    // 502 still carries a typed payload from the server.
    return { source: 'direct', backends: [], error: `HTTP ${res.status}` };
  }
  return await res.json() as WorkerBackendsResponse;
}

// ── Side Info Panel ────────────────────────────────────────────────────────

export interface NodeStatus {
  nodeId: string;
  workerId: string;
  source: 'direct' | 'proxy';
  online: boolean;
  busy: boolean;
  busySlots: number;
  totalSlots: number;
  loadedModel: string | null;
  throughputTps: number | null;
  lastSeen: string;
  lastProbeError?: string;
}

export interface WorkerStatusBackendRow {
  id: string;
  state: 'idle' | 'running';
  busySlots: number;
  totalSlots: number;
  online: boolean | null;
}

export interface WorkerStatusRow {
  id: string;
  name: string;
  roles: string[];
  state: 'idle' | 'running';
  /** True when this row represents a `proxy: true` worker. */
  proxy: boolean;
  /** Slot pressure from BackendStatusRegistry. Populated for direct workers with a registry probe row. */
  busySlots?: number;
  totalSlots?: number;
  online?: boolean;
  /** Per-backend rows for proxy workers (Phase 3c + dashboard tree). */
  backends?: WorkerStatusBackendRow[];
  /**
   * Occupants of this worker — users whose running jobs use it, each with
   * the job kind (`task_kind`: 'agent' = normal, 'reflection' = learning).
   * Admin-only — the server only populates this for admins, so non-admin
   * clients always receive undefined.
   */
  occupants?: Array<{ user: string; kind: string }>;
}

export async function fetchWorkerStatuses(): Promise<WorkerStatusRow[]> {
  const res = await fetch('/api/local/dashboard/workers');
  if (!res.ok) throw new Error(`Failed to list worker statuses: ${res.status}`);
  return (await res.json()).workers;
}

/** Thrown by fetchNodeStatus when the registry is not configured (HTTP 503). */
export class NodeStatusUnavailableError extends Error {
  constructor() {
    super('node-status registry not configured');
    this.name = 'NodeStatusUnavailableError';
  }
}

export async function fetchNodeStatus(): Promise<NodeStatus[]> {
  const res = await fetch('/api/local/dashboard/node-status');
  if (!res.ok) {
    // 503 = registry not configured (e.g. legacy install). Surface as an
    // error so the React Query hook can back off polling instead of
    // hammering the server every 5s indefinitely. The hook turns this
    // into an empty list for rendering.
    if (res.status === 503) throw new NodeStatusUnavailableError();
    throw new Error(`Failed to list node status: ${res.status}`);
  }
  return (await res.json()).nodes;
}
