// Schmaler Fetch-Helfer gegen das MC-2-Backend (/api/*).

export async function api<T = unknown>(path: string, init?: RequestInit): Promise<T> {
  const headers = {
    "Content-Type": "application/json",
    ...init?.headers,
  } as Record<string, string>

  const method = init?.method?.toUpperCase() || "GET"

  // Geheimnisse (Box-Passwort, HF-Token) reisen NUR bei mutierenden Requests mit —
  // die Dauer-Polls (GET) brauchen sie nie und sollen sie auch nie im Netz tragen.
  const sudoPassword = method === "GET" ? null : localStorage.getItem("mc_sudo_password")
  const hfToken = method === "GET" ? null : localStorage.getItem("mc_hf_token")

  if (sudoPassword) {
    headers["X-Sudo-Password"] = sudoPassword
  }

  let body = init?.body
  if (method === "POST") {
    if (typeof body === "string") {
      try {
        const data = JSON.parse(body)
        let changed = false
        if (sudoPassword && !("sudo_password" in data)) {
          data["sudo_password"] = sudoPassword
          changed = true
        }
        if (hfToken && !("hf_token" in data)) {
          data["hf_token"] = hfToken
          changed = true
        }
        if (changed) {
          body = JSON.stringify(data)
        }
      } catch (e) {
        // ignore JSON parse errors
      }
    } else if (!body) {
      const data: Record<string, any> = {}
      if (sudoPassword) data["sudo_password"] = sudoPassword
      if (hfToken) data["hf_token"] = hfToken
      if (Object.keys(data).length > 0) {
        body = JSON.stringify(data)
      }
    }
  }

  const res = await fetch(path, {
    ...init,
    headers,
    body,
  })
  if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
  return res.json() as Promise<T>
}

// llama-swap-`groups`: Mitglieder mit swap=false dürfen GLEICHZEITIG resident sein (Ko-Residenz).
// Die `brains`-Gruppe hält Hirn (fast) + Augen (vision) gemeinsam warm, statt sich zu verdrängen.
export interface GroupSpec {
  swap: boolean
  persist: boolean
  members: string[]
}
export interface GroupsResp {
  groups: Record<string, GroupSpec>
}
export const getGroups = () => api<GroupsResp>("/api/groups")
export const setGroup = (group: string, members: string[], swap = false, persist = true) =>
  api("/api/groups", { method: "PUT", body: JSON.stringify({ group, members, swap, persist }) })

export interface Capabilities {
  moe: boolean
  active_b: number | null
  tools: "yes" | "likely" | "no"
  vision: boolean
  coder: boolean
  reasoning: boolean
  embedding: boolean
  ctx: number | null
  params_b: number | null
  arch: string | null
}

export interface Fit {
  level: "perfect" | "marginal" | "too_tight"
  text: string
  req_gb: number
  tps: number
}

export interface RoleRecModel {
  name: string
  current_role: string | null
  params_b: number
  quant: string
  fit: Fit
  suitable: boolean
  incomplete: boolean
  score: number
  reason: string
  recommended: boolean
}

export interface RoleRecResp {
  role: string
  recommended: string | null
  models: RoleRecModel[]
}

export interface FitResp {
  params_b: number
  fit: Fit
  optimal_ctx: number
  assigned_ctx: number
  budget: { gtt_gb: number; reserved_gb: number; budget_gb: number; mode: string }
  sys_ram_gb: number
}

export interface ModelInfo {
  name: string
  role: string | null
  aliases: string[]
  api_ids: string[]
  ctx: number | null
  ttl: number | null
  cmd: string
  gguf_path: string
  filename: string
  quant: string
  size_bytes: number | null
  incomplete: boolean
  prompt_cache: boolean
  spec_draft_model: string | null
  spec_type: string | null
  spec_active: boolean
  parallel_slots: number
  capabilities: Capabilities
}

export interface VocabFingerprint {
  model: string | null
  pre: string | null
  n_vocab: number | null
  arch: string | null
}

export interface DraftInfo {
  path: string
  filename: string
  size_bytes: number | null
  vocab: VocabFingerprint | null
  compatible: boolean | null   // null = nicht prüfbar (Ziel-GGUF fehlt)
  mtp?: boolean                // MTP-Kopf (Multi-Token-Prediction) statt klassischem Draft
}

export interface DraftsResp {
  target_path: string
  target_exists: boolean
  target_vocab: VocabFingerprint | null
  drafts: DraftInfo[]
}

export interface DiscoverModel {
  name: string
  author: string
  repo: string
  role: string
  params_b: number
  quant: string
  tags: string[]
  downloads: number
  fit: Fit
  optimal_ctx: number
  caps: Capabilities
}

export interface DiscoverCategory {
  role: string
  title: string
  icon: string
  models: DiscoverModel[]
  recommended: string | null
}

export interface DiscoverResp {
  updated: number
  categories: DiscoverCategory[]
  sys_ram_gb: number
}

export interface RoutingResp {
  mode?: string
  endpoint?: string
  heavy_threshold_chars?: number
  routes: { name: string; target: string }[]
  lanes?: { name: string; target: string; threshold_chars?: number; escalate_chars?: number; aka?: string }[]
  fallbacks: Record<string, string[]>[]
  context_window_fallbacks: Record<string, string[]>[]
  gateway_reachable: boolean
}

// UI-editierbare Routing-Policy (GET/PUT /api/routing/policy). Aliase + Zeichen-Schwellen
// hinter den Lanes; hot-reload im Backend (kein Restart).
export interface RoutingPolicy {
  fast: string
  heavy: string
  coder: string
  coder_lite: string
  heavy_chars: number
  coding_escalate_chars: number
  fast_no_think: boolean
}
export interface RoutingPolicyField {
  key: keyof RoutingPolicy
  label: string
  type: "str" | "int" | "bool"
  min?: number
  max?: number
}
export interface RoutingPolicyMeta {
  policy: RoutingPolicy
  defaults: RoutingPolicy
  fields: RoutingPolicyField[]
}
export const getRoutingPolicy = () => api<RoutingPolicyMeta>("/api/routing/policy")
export const updateRoutingPolicy = (patch: Partial<RoutingPolicy>) =>
  api<{ policy: RoutingPolicy }>("/api/routing/policy", { method: "PUT", body: JSON.stringify(patch) })

export interface GitInfo {
  hash: string
  date: string
  subject: string
  branch: string
  dirty: boolean
  path: string
}

// Engine kann git-, binary- oder unbekannte Version sein — Felder je nach `type`.
export interface ComponentVersion {
  type: "git" | "binary" | "unknown"
  hash?: string
  date?: string
  subject?: string
  branch?: string
  dirty?: boolean
  path?: string
  version_text?: string
}

export interface Versions {
  mc2: GitInfo | null
  engine: ComponentVersion
  hermes_agent: GitInfo | null
}

export interface SystemStatus {
  cpu: { percent: number; cores: number | null }
  ram: { total: number; used: number; percent: number }
  gpu: {
    busy_percent: number | null
    vram_used: number | null
    vram_total: number | null
    gtt_used?: number | null
    gtt_total?: number | null
  } | null
  temp: { cpu?: number; gpu?: number } | null
  disk: { total: number; used: number; percent: number } | null
  versions?: Versions
}

export interface ServicesResp {
  services: { name: string; unit: string; url: string; ok: boolean }[]
  links: { engine_ui: string; gateway: string; hermes_ui: string }
}

export interface ComponentUpdate {
  key: string                 // z.B. "hermes_agent"
  name: string
  current: string | null
  latest: string | null
  update: boolean | null      // null = unbestimmbar (installierte Version remote nicht abfragbar)
  reachable: boolean | null
}

export interface UpdatesResp {
  os: number
  engine: number
  swap: number
  models: number
  model_list: { role: string; title: string; repo: string }[]
  last_check?: number | null
  components?: ComponentUpdate[]
}

export interface UpdateDetails {
  kind: "os" | "engine" | "swap" | "hermes"
  error?: string
  // LLM-Zusammenfassung in Lucys Stimme (hermes/engine/swap; Breaking Changes zuerst)
  summary?: string
  // Aktions-Verdikt: muss der Besitzer selbst etwas tun? (null = kein Verdikt/keine Summary)
  action_needed?: boolean | null
  action_text?: string
  // os
  count?: number
  packages?: { name: string; current: string; candidate: string }[]
  // os: vom System zurückgestellte Pakete (Phasen-Rollout / kept back) — ehrlich statt "hängt"
  held_back?: { name: string; reason: "phasing" | "kept_back" }[]
  // engine
  installed_build?: number | null
  latest_build?: number | null
  latest_tag?: string | null
  name?: string | null
  url?: string | null
  body?: string | null
  // hermes
  branch?: string | null
  behind?: number
  commits?: { hash: string; subject: string; when: string }[]
}

export interface ConnectTool {
  label: string
  lang: string
  snippet: string
  note: string
}
export interface ConnectResp {
  host: string
  gateway_url: string
  mc_url: string
  tools: Record<string, ConnectTool>   // Leitung 1 — Modell (IDEs/Agenten → Gateway)
  memory: ConnectTool                   // Leitung 2 — Gedächtnis (separater MCP-Server)
}

export interface ConnectLine {
  ok: boolean
  detail: string
}
export interface ConnectHealth {
  gateway: ConnectLine
  memory: ConnectLine
  desktop_gateway: ConnectLine
}

export interface Memory {
  id: string
  content: string
  category: string
  source: string
  created_at: string
  updated_at: string
  score?: number   // Relevanz bei semantischer Suche (q gesetzt); sonst undefined
}

export interface MemoryGraphNode {
  id: string
  content: string
  category: string
  source: string
}
export interface MemoryGraph {
  nodes: MemoryGraphNode[]
  edges: { source: string; target: string; weight: number }[]
}

export interface DedupeResult {
  groups: { keep: { id: string; content: string; category: string }; remove: { id: string; content: string }[] }[]
  duplicate_count: number
  removed: number
  applied: boolean
}

export interface AgentStatus {
  gateway_url: string
  box_console_url?: string
  hermes_ui_url?: string
  gateway_reachable: boolean
  box_console_reachable?: boolean
  hermes_ui_reachable?: boolean
  home_exists: boolean
  brain_model?: string
  has_config: boolean
  has_skills: boolean
  has_memories: boolean
  telegram_enabled?: boolean
  mcp_server_count?: number
  pc_executor_reachable?: boolean
}

export interface Job {
  id: string
  label: string
  group?: string | null          // "maintenance" = system-veränderndes Update (Wartungs-Riegel)
  state: "queued" | "running" | "done" | "failed" | "canceled"
  progress?: number
  total_bytes?: number
  done_bytes?: number
  rate_bps?: number
  eta_s?: number
}

export interface Health {
  status: string
  version: string
  engine_reachable: boolean
  gateway_reachable: boolean
  brain?: { role: string; model: string | null; ready: boolean }
}

export interface TokenStats {
  prompt_tokens: number
  completion_tokens: number
  total_tokens: number
  saved_usd: number
  saved_eur: number
  pricing?: Record<string, { in: number; out: number }>
}

// Per-Turn-Latenz-Trace (GET /api/voice/trace). Balken-Stufen (zeitlich disjunkt):
// stt · vision · hirn · gen. mem0 = Unter-Detail INNERHALB von hirn (nicht zum Balken addieren).
export interface VoiceTurn {
  id: string
  ts: number          // Epoch-Sekunden
  session: string
  kind: string
  had_images: boolean
  stt_ms: number | null
  vision_ms: number | null
  hirn_ms: number | null   // Zeit bis erstes Inhalts-Token (Agent + Mem0 + LLM-TTFT)
  gen_ms: number | null    // Generierung nach dem ersten Token bis Stream-Ende
  mem0_ms: number | null   // Teil VON hirn (Mem0-Retrieve), best-effort
  total_ms: number
  error: string | null
}

export interface VoiceTraceResp {
  turns: VoiceTurn[]
}

export interface ModelsResp {
  models: ModelInfo[]
  running?: string[]
}

// ── Auftragsbuch (Vorschlags-Inbox) ──────────────────────────────────────────
export interface AuftragStatus {
  state: "laeuft" | "eingespielt" | "fehlgeschlagen" | "rollback" | "zurueckgerollt" | "kritisch" | "abgelehnt"
  detail: string
  ts: number
}
export interface AuftragItem {
  repo?: "mc2" | "lucy"           // Quelle: Box-Stack (mc2) oder Lucy-Desktop (Annahme baut am PC)
  branch: string
  kind: string                    // wartung | orchestrator | doku | feature
  subject: string
  body: string
  author: string
  ts: number | null
  ahead: number
  behind: number
  shortstat: string
  files: { status: string; path: string }[]
  files_truncated: boolean
  frontend_ohne_build: boolean    // Frontend-Quelltext ohne dist → Box kann nicht bauen
  verwaist?: boolean              // kein gemeinsamer Ursprung mit main (kaputt aufgesetzt) → Merge unmöglich
  leer?: boolean                  // Diff gegen main ist leer → bringt nichts (schon in main / am Repo vorbei)
  status: AuftragStatus | null
  // Empfehlungs-Stempel des nächtlichen Karten-Gutachters (Meinung, kein Gate).
  empfehlung: { empfehlung: "ANNEHMEN" | "ABLEHNEN" | "UNKLAR"; satz: string; richter: string } | null
}
export interface SkillKandidat {
  file: string
  title: string
  preview: string
  mtime: number
}
export interface AuftragsbuchResp {
  available: boolean
  items: AuftragItem[]
  skill_kandidaten: SkillKandidat[]
  open_count: number
}

// ── Ideen-Queue (natives Hermes-Kanban) ──────────────────────────────────────
export interface IdeenItem {
  id: string
  titel: string
  body: string
  status: string                  // triage | todo | ready | running | blocked | review | done
  assignee: string | null
  erstellt: number | null
  fertig: number | null
  von: string | null
  frage?: string | null          // bei status=blocked: die Frage/der Grund des Workers
  frage_kind?: string | null     // needs_input | capability | transient | ""
}
export interface IdeenLogResp {
  available: boolean
  lines: string[]
}
export interface IdeenResp {
  available: boolean
  items: IdeenItem[]
  offen: number
  fehler?: string
}

// ── Chronik (Timeline aus dem Melde-Briefkasten) ─────────────────────────────
export interface ChronikItem {
  id: number
  ts: number
  subject: string
  text: string
  source: string
  priority: "normal" | "silent"
}
export interface ChronikResp {
  items: ChronikItem[]
}

// ── Wissens-Vault (Traum-Notizen, read-only) ─────────────────────────────────
export interface WissenFile {
  path: string
  name: string
  dir: string
  title: string
  mtime: number
  neu: boolean
}
export interface WissenResp {
  available: boolean
  files: WissenFile[]
}
export interface WissenDatei {
  path: string
  content: string
  mtime: number
}

// ── Zeitmaschine (Snapshots + Restore) ───────────────────────────────────────
export interface Snapshot {
  snapshot: string
  file: string
  size_mb: number
}
export interface ZeitmaschineResp {
  available: boolean
  backups: Snapshot[]
}

// ── Erinnerungen (Wecker/Routinen) ───────────────────────────────────────────
export interface Reminder {
  id: number
  text: string
  next_ts: number
  repeat: string          // '' einmalig | daily | weekdays | weekly
  created_ts: number
}

export interface HermesBrainModel {
  name: string
  filename?: string
  params_b: number | null
  quant?: string
  size_bytes?: number | null
  version?: number | null
  gguf_path?: string
  incomplete?: boolean
}

export interface HermesBrainCandidate {
  repo: string
  name: string
  version: number
  params_b: number
  downloads: number
  fit: Fit
}

export interface HermesBrainBudget {
  gtt_gb: number
  brain_gb: number            // Footprint des (empfohlenen) Brains, das immer resident bleibt
  warm_projected_gb: number   // alle persist-Modelle warm (Info)
  free_after_gb: number       // frei nach Brain + größtem on-demand
  largest_ondemand_gb: number // größtes on-demand-Modell (z.B. heavy/coder)
  fits: boolean               // passt Brain + größtes on-demand zusammen ins Budget?
}

export interface HermesBrainResp {
  current: HermesBrainModel | null
  recommended: HermesBrainCandidate | null
  update_available: boolean
  budget?: HermesBrainBudget | null
}

// ── Ideen-Log (Live Worker Log) ──────────────────────────────────────────────
export const getIdeenLog = (id: string) => api<IdeenLogResp>(`/api/ideen/${id}/log`)
