import { API_CONFIG } from './config'
import { apiUrl } from './basePath'

const enc = encodeURIComponent
const userQ = (userId) => userId ? `?user_id=${enc(userId)}` : ''

async function handleResponse(response) {
  if (!response.ok) {
    let errorMessage = `HTTP ${response.status}`
    let errorBody = null
    try {
      const data = await response.json()
      errorBody = data
      if (data?.error?.message) errorMessage = data.error.message
      else if (data?.error) errorMessage = data.error
    } catch (_e) {
      // response wasn't JSON
    }
    const err = new Error(errorMessage)
    // Preserve the parsed body + status so handlers can pattern-match on
    // structured responses (e.g. the import form's ambiguity picker).
    err.status = response.status
    err.body = errorBody
    throw err
  }
  const contentType = response.headers.get('content-type')
  if (contentType && contentType.includes('application/json')) {
    return response.json()
  }
  return response
}

function buildUrl(endpoint, params) {
  const url = new URL(apiUrl(endpoint), window.location.origin)
  if (params) {
    Object.entries(params).forEach(([key, value]) => {
      if (value !== undefined && value !== null && value !== '') {
        url.searchParams.set(key, value)
      }
    })
  }
  return url.toString()
}

async function fetchJSON(endpoint, options = {}) {
  const response = await fetch(apiUrl(endpoint), {
    headers: { 'Content-Type': 'application/json', ...options.headers },
    ...options,
  })
  return handleResponse(response)
}

async function postJSON(endpoint, body, options = {}) {
  return fetchJSON(endpoint, {
    method: 'POST',
    body: JSON.stringify(body),
    ...options,
  })
}

// SSE streaming for chat completions
export async function streamChat(body, signal) {
  const response = await fetch(apiUrl(API_CONFIG.endpoints.chatCompletions), {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ...body, stream: true }),
    signal,
  })

  if (!response.ok) {
    let errorMessage = `HTTP ${response.status}`
    try {
      const data = await response.json()
      if (data?.error?.message) errorMessage = data.error.message
    } catch (_e) { /* not JSON */ }
    throw new Error(errorMessage)
  }

  return response.body
}

// Models API
export const modelsApi = {
  list: (params) => fetchJSON(buildUrl(API_CONFIG.endpoints.models, params)),
  listV1: () => fetchJSON(API_CONFIG.endpoints.modelsList),
  listCapabilities: () => fetchJSON(API_CONFIG.endpoints.modelsCapabilities),
  install: (id) => postJSON(API_CONFIG.endpoints.installModel(id), {}),
  delete: (id) => postJSON(API_CONFIG.endpoints.deleteModel(id), {}),
  estimate: (id, contexts) => fetchJSON(
    buildUrl(API_CONFIG.endpoints.modelEstimate(id),
      contexts?.length ? { contexts: contexts.join(',') } : {})
  ),
  getConfig: (id) => postJSON(API_CONFIG.endpoints.modelConfig(id), {}),
  getConfigJson: (name) => fetchJSON(API_CONFIG.endpoints.modelConfigJson(name)),
  getJob: (uid) => fetchJSON(API_CONFIG.endpoints.modelJob(uid)),
  apply: (body) => postJSON(API_CONFIG.endpoints.modelsApply, body),
  deleteByName: (name) => postJSON(API_CONFIG.endpoints.modelsDelete(name), {}),
  reload: () => postJSON(API_CONFIG.endpoints.modelsReload, {}),
  importUri: (body) => postJSON(API_CONFIG.endpoints.modelsImportUri, body),
  importConfig: async (content, contentType = 'application/x-yaml') => {
    const response = await fetch(apiUrl(API_CONFIG.endpoints.modelsImport), {
      method: 'POST',
      headers: { 'Content-Type': contentType },
      body: content,
    })
    return handleResponse(response)
  },
  getJobStatus: (uid) => fetchJSON(API_CONFIG.endpoints.modelsJobStatus(uid)),
  getEditConfig: (name) => fetchJSON(API_CONFIG.endpoints.modelEditGet(name)),
  editConfig: (name, body) => postJSON(API_CONFIG.endpoints.modelEdit(name), body),
  toggleState: (name, action) => fetchJSON(API_CONFIG.endpoints.modelToggleState(name, action), { method: 'PUT' }),
  togglePinned: (name, action) => fetchJSON(API_CONFIG.endpoints.modelTogglePinned(name, action), { method: 'PUT' }),
  getConfigMetadata: (section) => fetchJSON(
    section ? `${API_CONFIG.endpoints.configMetadata}?section=${section}`
            : API_CONFIG.endpoints.configMetadata
  ),
  getAutocomplete: (provider) => fetchJSON(API_CONFIG.endpoints.configAutocomplete(provider)),
  estimateVram: (body, options) => postJSON(API_CONFIG.endpoints.vramEstimate, body, options),
  patchConfig: (name, patch) => fetchJSON(API_CONFIG.endpoints.configPatch(name), {
    method: 'PATCH',
    body: JSON.stringify(patch),
  }),
  backendUsecases: () => fetchJSON('/api/backends/usecases'),
}

// Backends API
export const backendsApi = {
  list: (params) => fetchJSON(buildUrl(API_CONFIG.endpoints.backends, params)),
  listInstalled: () => fetchJSON(API_CONFIG.endpoints.backendsInstalled),
  listKnown: () => fetchJSON(API_CONFIG.endpoints.backendsKnown),
  install: (id) => postJSON(API_CONFIG.endpoints.installBackend(id), {}),
  delete: (id) => postJSON(API_CONFIG.endpoints.deleteBackend(id), {}),
  installExternal: (body) => postJSON(API_CONFIG.endpoints.installExternalBackend, body),
  getJob: (uid) => fetchJSON(API_CONFIG.endpoints.backendJob(uid)),
  deleteInstalled: (name) => postJSON(API_CONFIG.endpoints.deleteInstalledBackend(name), {}),
  checkUpgrades: () => fetchJSON(API_CONFIG.endpoints.backendsUpgrades),
  forceCheckUpgrades: () => postJSON(API_CONFIG.endpoints.backendsUpgradesCheck, {}),
  upgrade: (name) => postJSON(API_CONFIG.endpoints.upgradeBackend(name), {}),
}

// Chat API (non-streaming)
export const chatApi = {
  complete: (body) => postJSON(API_CONFIG.endpoints.chatCompletions, body),
  mcpComplete: (body) => postJSON(API_CONFIG.endpoints.mcpChatCompletions, body),
}

// MCP API
export const mcpApi = {
  listServers: (model) => fetchJSON(API_CONFIG.endpoints.mcpServers(model)),
  listPrompts: (model) => fetchJSON(API_CONFIG.endpoints.mcpPrompts(model)),
  getPrompt: (model, name, args) => postJSON(API_CONFIG.endpoints.mcpGetPrompt(model, name), { arguments: args }),
  listResources: (model) => fetchJSON(API_CONFIG.endpoints.mcpResources(model)),
  readResource: (model, uri) => postJSON(API_CONFIG.endpoints.mcpReadResource(model), { uri }),
}

// Resources API
export const resourcesApi = {
  get: () => fetchJSON(API_CONFIG.endpoints.resources),
}

// Operations API
export const operationsApi = {
  list: () => fetchJSON(API_CONFIG.endpoints.operations),
  cancel: (jobID) => postJSON(API_CONFIG.endpoints.cancelOperation(jobID), {}),
  dismiss: (jobID) => postJSON(API_CONFIG.endpoints.dismissOperation(jobID), {}),
}

// Settings API
export const settingsApi = {
  get: () => fetchJSON(API_CONFIG.endpoints.settings),
  save: (body) => postJSON(API_CONFIG.endpoints.settings, body),
}

// Branding / whitelabeling
// /api/branding is public (no auth) — the login page reads it before the
// user signs in. Asset uploads/deletes still require admin privileges.
export const brandingApi = {
  get: () => fetchJSON('/api/branding'),
  uploadAsset: (kind, file) => {
    const fd = new FormData()
    fd.append('file', file)
    return fetch(apiUrl(`/api/branding/asset/${enc(kind)}`), {
      method: 'POST',
      body: fd,
      credentials: 'include',
    }).then(handleResponse)
  },
  deleteAsset: (kind) => fetch(apiUrl(`/api/branding/asset/${enc(kind)}`), {
    method: 'DELETE',
    credentials: 'include',
  }).then(handleResponse),
}

// Backend Logs API
export const backendLogsApi = {
  listModels: () => fetchJSON(API_CONFIG.endpoints.backendLogs),
  getLines: (modelId) => fetchJSON(API_CONFIG.endpoints.backendLogsModel(modelId)),
  clear: (modelId) => postJSON(API_CONFIG.endpoints.clearBackendLogs(modelId), {}),
}

// Traces API
export const tracesApi = {
  get: () => fetchJSON(API_CONFIG.endpoints.traces),
  clear: () => postJSON(API_CONFIG.endpoints.clearTraces, {}),
  getBackend: () => fetchJSON(API_CONFIG.endpoints.backendTraces),
  clearBackend: () => postJSON(API_CONFIG.endpoints.clearBackendTraces, {}),
}

// P2P API
export const p2pApi = {
  getWorkers: () => fetchJSON(API_CONFIG.endpoints.p2pWorkers),
  getFederation: () => fetchJSON(API_CONFIG.endpoints.p2pFederation),
  getStats: () => fetchJSON(API_CONFIG.endpoints.p2pStats),
  getToken: async () => {
    const response = await fetch(apiUrl(API_CONFIG.endpoints.p2pToken))
    if (!response.ok) throw new Error(`HTTP ${response.status}`)
    return response.text()
  },
}

// Agent Jobs API
export const agentJobsApi = {
  listTasks: (allUsers) => fetchJSON(`${API_CONFIG.endpoints.agentTasks}${allUsers ? '?all_users=true' : ''}`),
  getTask: (id) => fetchJSON(API_CONFIG.endpoints.agentTask(id)),
  createTask: (body) => postJSON(API_CONFIG.endpoints.agentTasks, body),
  updateTask: (id, body) => fetchJSON(API_CONFIG.endpoints.agentTask(id), { method: 'PUT', body: JSON.stringify(body), headers: { 'Content-Type': 'application/json' } }),
  deleteTask: (id) => fetchJSON(API_CONFIG.endpoints.agentTask(id), { method: 'DELETE' }),
  executeTask: (name, body = {}) => postJSON(API_CONFIG.endpoints.executeAgentTask(name), body),
  listJobs: (allUsers) => fetchJSON(`${API_CONFIG.endpoints.agentJobs}${allUsers ? '?all_users=true' : ''}`),
  getJob: (id) => fetchJSON(API_CONFIG.endpoints.agentJob(id)),
  cancelJob: (id) => postJSON(API_CONFIG.endpoints.cancelAgentJob(id), {}),
  executeJob: (body) => postJSON(API_CONFIG.endpoints.executeAgentJob, body),
}

// Image generation
export const imageApi = {
  generate: (body) => postJSON(API_CONFIG.endpoints.imageGenerations, body),
}

// Video generation
export const videoApi = {
  generate: (body) => postJSON(API_CONFIG.endpoints.video, body),
}

// parseAudioBlobResponse — shared response handling for audio-blob endpoints.
// Throws on non-2xx (with the API error message when present); returns the
// blob plus the parsed Content-Disposition filename mapped to the server's
// /generated-audio/ path so the UI can persist it in history. The audio
// transform endpoint also surfaces the persisted *input* paths via
// X-Audio-Input-Url / X-Audio-Reference-Url headers so the UI can replay
// past (input, reference, output) triples from history.
async function parseAudioBlobResponse(response) {
  if (!response.ok) {
    const data = await response.json().catch(() => ({}))
    throw new Error(data?.error?.message || `HTTP ${response.status}`)
  }
  let serverUrl = null
  const disposition = response.headers.get('content-disposition')
  if (disposition) {
    const match = disposition.match(/filename[^;=\n]*=["']?([^"';\n]*)["']?/)
    if (match && match[1]) serverUrl = '/generated-audio/' + match[1]
  }
  const inputUrl = response.headers.get('x-audio-input-url') || null
  const referenceUrl = response.headers.get('x-audio-reference-url') || null
  const blob = await response.blob()
  return { blob, serverUrl, inputUrl, referenceUrl }
}

async function postAudioBlob(endpoint, body) {
  const response = await fetch(apiUrl(endpoint), {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  })
  return parseAudioBlobResponse(response)
}

async function postMultipartAudioBlob(endpoint, formData) {
  const response = await fetch(apiUrl(endpoint), { method: 'POST', body: formData })
  return parseAudioBlobResponse(response)
}

// TTS
export const ttsApi = {
  generate: (body) => postAudioBlob(API_CONFIG.endpoints.tts, body),
  generateV1: (body) => postAudioBlob(API_CONFIG.endpoints.audioSpeech, body),
}

// Sound generation
export const soundApi = {
  generate: (body) => postAudioBlob(API_CONFIG.endpoints.soundGeneration, body),
}

// Audio transform (echo cancellation, noise suppression, voice conversion, etc.)
export const audioTransformApi = {
  process: ({ model, audioFile, referenceFile, format, sampleRate, params }) => {
    const fd = new FormData()
    fd.append('model', model)
    fd.append('audio', audioFile, audioFile?.name || 'audio.wav')
    if (referenceFile) fd.append('reference', referenceFile, referenceFile.name || 'reference.wav')
    if (format) fd.append('response_format', format)
    if (sampleRate) fd.append('sample_rate', String(sampleRate))
    if (params) {
      for (const [k, v] of Object.entries(params)) {
        if (v == null || v === '') continue
        fd.append(`params[${k}]`, String(v))
      }
    }
    return postMultipartAudioBlob(API_CONFIG.endpoints.audioTransformations, fd)
  },
  streamUrl: () => apiUrl(API_CONFIG.endpoints.audioTransformStream).replace(/^http/, 'ws'),
}

// Audio transcription
export const audioApi = {
  transcribe: async (formData) => {
    const response = await fetch(apiUrl(API_CONFIG.endpoints.audioTranscriptions), {
      method: 'POST',
      body: formData,
    })
    return handleResponse(response)
  },
}

// Face biometrics — backend spec: core/http/endpoints/localai/face_*.go
export const faceApi = {
  verify: (body) => postJSON(API_CONFIG.endpoints.faceVerify, body),
  analyze: (body) => postJSON(API_CONFIG.endpoints.faceAnalyze, body),
  embed: (body) => postJSON(API_CONFIG.endpoints.faceEmbed, body),
  register: (body) => postJSON(API_CONFIG.endpoints.faceRegister, body),
  identify: (body) => postJSON(API_CONFIG.endpoints.faceIdentify, body),
  forget: (body) => postJSON(API_CONFIG.endpoints.faceForget, body),
}

// Voice biometrics — backend spec: core/http/endpoints/localai/voice_*.go
export const voiceApi = {
  verify: (body) => postJSON(API_CONFIG.endpoints.voiceVerify, body),
  analyze: (body) => postJSON(API_CONFIG.endpoints.voiceAnalyze, body),
  embed: (body) => postJSON(API_CONFIG.endpoints.voiceEmbed, body),
  register: (body) => postJSON(API_CONFIG.endpoints.voiceRegister, body),
  identify: (body) => postJSON(API_CONFIG.endpoints.voiceIdentify, body),
  forget: (body) => postJSON(API_CONFIG.endpoints.voiceForget, body),
}

// Realtime / WebRTC
export const realtimeApi = {
  call: (body) => postJSON(API_CONFIG.endpoints.realtimeCalls, body),
  pipelineModels: () => fetchJSON(API_CONFIG.endpoints.pipelineModels),
}

// Backend control
export const backendControlApi = {
  shutdown: (body) => postJSON(API_CONFIG.endpoints.backendShutdown, body),
}

// System info
export const systemApi = {
  version: () => fetchJSON(API_CONFIG.endpoints.version),
  info: () => fetchJSON(API_CONFIG.endpoints.system),
}

export const agentsApi = {
  list: (allUsers) => fetchJSON(`/api/agents${allUsers ? '?all_users=true' : ''}`),
  create: (config) => postJSON('/api/agents', config),
  get: (name, userId) => fetchJSON(`/api/agents/${enc(name)}${userQ(userId)}`),
  getConfig: (name, userId) => fetchJSON(`/api/agents/${enc(name)}/config${userQ(userId)}`),
  update: (name, config, userId) => fetchJSON(`/api/agents/${enc(name)}${userQ(userId)}`, { method: 'PUT', body: JSON.stringify(config), headers: { 'Content-Type': 'application/json' } }),
  delete: (name, userId) => fetchJSON(`/api/agents/${enc(name)}${userQ(userId)}`, { method: 'DELETE' }),
  pause: (name, userId) => fetchJSON(`/api/agents/${enc(name)}/pause${userQ(userId)}`, { method: 'PUT' }),
  resume: (name, userId) => fetchJSON(`/api/agents/${enc(name)}/resume${userQ(userId)}`, { method: 'PUT' }),
  status: (name, userId) => fetchJSON(`/api/agents/${enc(name)}/status${userQ(userId)}`),
  observables: (name, userId) => fetchJSON(`/api/agents/${enc(name)}/observables${userQ(userId)}`),
  clearObservables: (name, userId) => fetchJSON(`/api/agents/${enc(name)}/observables${userQ(userId)}`, { method: 'DELETE' }),
  chat: (name, message, userId) => postJSON(`/api/agents/${enc(name)}/chat${userQ(userId)}`, { message }),
  export: (name, userId) => fetchJSON(`/api/agents/${enc(name)}/export${userQ(userId)}`),
  import: (formData) => fetch(apiUrl('/api/agents/import'), { method: 'POST', body: formData }).then(handleResponse),
  configMeta: () => fetchJSON('/api/agents/config/metadata'),
  sseUrl: (name, userId) => `/api/agents/${enc(name)}/sse${userQ(userId)}`,
}

export const agentCollectionsApi = {
  list: (allUsers) => fetchJSON(`/api/agents/collections${allUsers ? '?all_users=true' : ''}`),
  create: (name) => postJSON('/api/agents/collections', { name }),
  upload: (name, formData, userId) => fetch(apiUrl(`/api/agents/collections/${enc(name)}/upload${userQ(userId)}`), { method: 'POST', body: formData }).then(handleResponse),
  entries: (name, userId) => fetchJSON(`/api/agents/collections/${enc(name)}/entries${userQ(userId)}`),
  entryContent: (name, entry, userId) => fetchJSON(`/api/agents/collections/${enc(name)}/entries/${encodeURIComponent(entry)}${userQ(userId)}`),
  search: (name, query, maxResults, userId) => postJSON(`/api/agents/collections/${enc(name)}/search${userQ(userId)}`, { query, max_results: maxResults }),
  reset: (name, userId) => postJSON(`/api/agents/collections/${enc(name)}/reset${userQ(userId)}`),
  deleteEntry: (name, entry, userId) => fetchJSON(`/api/agents/collections/${enc(name)}/entry/delete${userQ(userId)}`, { method: 'DELETE', body: JSON.stringify({ entry }), headers: { 'Content-Type': 'application/json' } }),
  sources: (name, userId) => fetchJSON(`/api/agents/collections/${enc(name)}/sources${userQ(userId)}`),
  addSource: (name, url, interval, userId) => postJSON(`/api/agents/collections/${enc(name)}/sources${userQ(userId)}`, { url, update_interval: interval }),
  removeSource: (name, url, userId) => fetchJSON(`/api/agents/collections/${enc(name)}/sources${userQ(userId)}`, { method: 'DELETE', body: JSON.stringify({ url }), headers: { 'Content-Type': 'application/json' } }),
}

// Skills API
export const skillsApi = {
  list: (allUsers) => fetchJSON(`/api/agents/skills${allUsers ? '?all_users=true' : ''}`),
  search: (q) => fetchJSON(`/api/agents/skills/search?q=${enc(q)}`),
  get: (name, userId) => fetchJSON(`/api/agents/skills/${enc(name)}${userQ(userId)}`),
  create: (data) => postJSON('/api/agents/skills', data),
  update: (name, data, userId) => fetchJSON(`/api/agents/skills/${enc(name)}${userQ(userId)}`, { method: 'PUT', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }),
  delete: (name, userId) => fetchJSON(`/api/agents/skills/${enc(name)}${userQ(userId)}`, { method: 'DELETE' }),
  import: (file) => { const fd = new FormData(); fd.append('file', file); return fetch(apiUrl('/api/agents/skills/import'), { method: 'POST', body: fd }).then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }); },
  exportUrl: (name, userId) => apiUrl(`/api/agents/skills/export/${enc(name)}${userQ(userId)}`),
  listResources: (name, userId) => fetchJSON(`/api/agents/skills/${enc(name)}/resources${userQ(userId)}`),
  getResource: (name, path, opts, userId) => fetchJSON(`/api/agents/skills/${enc(name)}/resources/${path}${opts?.json ? '?encoding=base64' : ''}${userId ? `${opts?.json ? '&' : '?'}user_id=${enc(userId)}` : ''}`),
  createResource: (name, path, file) => { const fd = new FormData(); fd.append('file', file); fd.append('path', path); return fetch(apiUrl(`/api/agents/skills/${enc(name)}/resources`), { method: 'POST', body: fd }).then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }); },
  updateResource: (name, path, content) => postJSON(`/api/agents/skills/${enc(name)}/resources/${path}`, { content }),
  deleteResource: (name, path) => fetchJSON(`/api/agents/skills/${enc(name)}/resources/${path}`, { method: 'DELETE' }),
  listGitRepos: () => fetchJSON('/api/agents/git-repos'),
  addGitRepo: (url) => postJSON('/api/agents/git-repos', { url }),
  syncGitRepo: (id) => postJSON(`/api/agents/git-repos/${enc(id)}/sync`, {}),
  toggleGitRepo: (id) => postJSON(`/api/agents/git-repos/${enc(id)}/toggle`, {}),
  deleteGitRepo: (id) => fetchJSON(`/api/agents/git-repos/${enc(id)}`, { method: 'DELETE' }),
}

// Usage API
export const usageApi = {
  getMyUsage: (period) => fetchJSON(`/api/auth/usage?period=${period || 'month'}`),
  getAdminUsage: (period, userId) => {
    let url = `/api/auth/admin/usage?period=${period || 'month'}`
    if (userId) url += `&user_id=${encodeURIComponent(userId)}`
    return fetchJSON(url)
  },
  getMyQuotas: () => fetchJSON('/api/auth/quota'),
}

// Admin Users API
export const adminUsersApi = {
  list: () => fetchJSON('/api/auth/admin/users'),
  setRole: (id, role) => fetchJSON(`/api/auth/admin/users/${encodeURIComponent(id)}/role`, {
    method: 'PUT', body: JSON.stringify({ role }), headers: { 'Content-Type': 'application/json' },
  }),
  delete: (id) => fetchJSON(`/api/auth/admin/users/${encodeURIComponent(id)}`, { method: 'DELETE' }),
  setStatus: (id, status) => fetchJSON(`/api/auth/admin/users/${encodeURIComponent(id)}/status`, {
    method: 'PUT', body: JSON.stringify({ status }), headers: { 'Content-Type': 'application/json' },
  }),
  getPermissions: (id) => fetchJSON(`/api/auth/admin/users/${encodeURIComponent(id)}/permissions`),
  setPermissions: (id, perms) => fetchJSON(`/api/auth/admin/users/${encodeURIComponent(id)}/permissions`, {
    method: 'PUT', body: JSON.stringify(perms), headers: { 'Content-Type': 'application/json' },
  }),
  getFeatures: () => fetchJSON('/api/auth/admin/features'),
  setModels: (id, allowlist) => fetchJSON(`/api/auth/admin/users/${encodeURIComponent(id)}/models`, {
    method: 'PUT', body: JSON.stringify(allowlist), headers: { 'Content-Type': 'application/json' },
  }),
  getQuotas: (id) => fetchJSON(`/api/auth/admin/users/${encodeURIComponent(id)}/quotas`),
  setQuota: (id, quota) => fetchJSON(`/api/auth/admin/users/${encodeURIComponent(id)}/quotas`, {
    method: 'PUT', body: JSON.stringify(quota), headers: { 'Content-Type': 'application/json' },
  }),
  deleteQuota: (id, quotaId) => fetchJSON(`/api/auth/admin/users/${encodeURIComponent(id)}/quotas/${encodeURIComponent(quotaId)}`, {
    method: 'DELETE',
  }),
  resetPassword: (id, password, acknowledgeWeak = false) => fetchJSON(`/api/auth/admin/users/${encodeURIComponent(id)}/password`, {
    method: 'PUT',
    body: JSON.stringify({ password, acknowledge_weak_password: acknowledgeWeak }),
    headers: { 'Content-Type': 'application/json' },
  }),
}

// Profile API
export const profileApi = {
  get: () => fetchJSON('/api/auth/me'),
  updateName: (name) => fetchJSON('/api/auth/profile', {
    method: 'PUT', body: JSON.stringify({ name }), headers: { 'Content-Type': 'application/json' },
  }),
  updateProfile: (name, avatarUrl) => fetchJSON('/api/auth/profile', {
    method: 'PUT', body: JSON.stringify({ name, avatar_url: avatarUrl || '' }), headers: { 'Content-Type': 'application/json' },
  }),
  changePassword: (currentPassword, newPassword, acknowledgeWeak = false) => fetchJSON('/api/auth/password', {
    method: 'PUT',
    body: JSON.stringify({ current_password: currentPassword, new_password: newPassword, acknowledge_weak_password: acknowledgeWeak }),
    headers: { 'Content-Type': 'application/json' },
  }),
}

// Admin Invites API
export const adminInvitesApi = {
  list: () => fetchJSON('/api/auth/admin/invites'),
  create: (expiresInHours = 168) => postJSON('/api/auth/admin/invites', { expiresInHours }),
  delete: (id) => fetchJSON(`/api/auth/admin/invites/${encodeURIComponent(id)}`, { method: 'DELETE' }),
}

// API Keys
export const apiKeysApi = {
  list: () => fetchJSON('/api/auth/api-keys'),
  create: (name) => postJSON('/api/auth/api-keys', { name }),
  revoke: (id) => fetchJSON(`/api/auth/api-keys/${encodeURIComponent(id)}`, { method: 'DELETE' }),
}

// Fine-tuning API
export const fineTuneApi = {
  listBackends: () => fetchJSON('/api/fine-tuning/backends'),
  startJob: (data) => postJSON('/api/fine-tuning/jobs', data),
  listJobs: () => fetchJSON('/api/fine-tuning/jobs'),
  getJob: (id) => fetchJSON(`/api/fine-tuning/jobs/${enc(id)}`),
  stopJob: (id, saveCheckpoint) => fetchJSON(`/api/fine-tuning/jobs/${enc(id)}/stop?save_checkpoint=${saveCheckpoint ? 'true' : 'false'}`, { method: 'POST' }),
  deleteJob: (id) => fetchJSON(`/api/fine-tuning/jobs/${enc(id)}`, { method: 'DELETE' }),
  listCheckpoints: (id) => fetchJSON(`/api/fine-tuning/jobs/${enc(id)}/checkpoints`),
  exportModel: (id, data) => postJSON(`/api/fine-tuning/jobs/${enc(id)}/export`, data),
  uploadDataset: (file) => {
    const formData = new FormData()
    formData.append('file', file)
    return fetch(apiUrl('/api/fine-tuning/datasets'), { method: 'POST', body: formData }).then(handleResponse)
  },
  progressUrl: (id) => apiUrl(`/api/fine-tuning/jobs/${enc(id)}/progress`),
  downloadUrl: (id) => apiUrl(`/api/fine-tuning/jobs/${enc(id)}/download`),
}

// Quantization API
export const quantizationApi = {
  listBackends: () => fetchJSON('/api/quantization/backends'),
  startJob: (data) => postJSON('/api/quantization/jobs', data),
  listJobs: () => fetchJSON('/api/quantization/jobs'),
  getJob: (id) => fetchJSON(`/api/quantization/jobs/${enc(id)}`),
  stopJob: (id) => fetchJSON(`/api/quantization/jobs/${enc(id)}/stop`, { method: 'POST' }),
  deleteJob: (id) => fetchJSON(`/api/quantization/jobs/${enc(id)}`, { method: 'DELETE' }),
  importModel: (id, data) => postJSON(`/api/quantization/jobs/${enc(id)}/import`, data),
  progressUrl: (id) => apiUrl(`/api/quantization/jobs/${enc(id)}/progress`),
  downloadUrl: (id) => apiUrl(`/api/quantization/jobs/${enc(id)}/download`),
}

// Nodes API (distributed)
export const nodesApi = {
  list: () => fetchJSON(API_CONFIG.endpoints.nodes),
  get: (id) => fetchJSON(API_CONFIG.endpoints.node(id)),
  delete: (id) => fetchJSON(API_CONFIG.endpoints.node(id), { method: 'DELETE' }),
  drain: (id) => postJSON(API_CONFIG.endpoints.nodeDrain(id), {}),
  resume: (id) => postJSON(API_CONFIG.endpoints.nodeResume(id), {}),
  approve: (id) => postJSON(API_CONFIG.endpoints.nodeApprove(id), {}),
  getModels: (id) => fetchJSON(API_CONFIG.endpoints.nodeModels(id)),
  getBackends: (id) => fetchJSON(API_CONFIG.endpoints.nodeBackends(id)),
  // installBackend installs a gallery backend on a single node. opts can
  // override the gallery path and supply a direct URI (OCI image / URL / file
  // path) plus an optional name+alias, mirroring the standalone /backends/
  // install-external surface but scoped to one node.
  installBackend: (id, backend, opts = {}) => postJSON(API_CONFIG.endpoints.nodeBackendsInstall(id), {
    backend,
    ...(opts.uri ? { uri: opts.uri } : {}),
    ...(opts.name ? { name: opts.name } : {}),
    ...(opts.alias ? { alias: opts.alias } : {}),
    ...(opts.backend_galleries ? { backend_galleries: opts.backend_galleries } : {}),
  }),
  deleteBackend: (id, backend) => postJSON(API_CONFIG.endpoints.nodeBackendsDelete(id), { backend }),
  getBackendLogs: (id) => fetchJSON(API_CONFIG.endpoints.nodeBackendLogs(id)),
  getBackendLogLines: (id, modelId) => fetchJSON(API_CONFIG.endpoints.nodeBackendLogsModel(id, modelId)),
  unloadModel: (id, modelName) => postJSON(API_CONFIG.endpoints.nodeModelsUnload(id), { model_name: modelName }),
  getLabels: (id) => fetchJSON(API_CONFIG.endpoints.nodeLabels(id)),
  mergeLabels: (id, labels) => fetchJSON(API_CONFIG.endpoints.nodeLabels(id), { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(labels) }),
  deleteLabel: (id, key) => fetchJSON(API_CONFIG.endpoints.nodeLabelKey(id, key), { method: 'DELETE' }),
  // Set a sticky admin override for the per-node replica cap. The override
  // is preserved across worker restarts; call resetMaxReplicasPerModel to
  // hand control back to the worker's CLI flag.
  updateMaxReplicasPerModel: (id, value) => fetchJSON(API_CONFIG.endpoints.nodeMaxReplicasPerModel(id), {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ value }),
  }),
  resetMaxReplicasPerModel: (id) => fetchJSON(API_CONFIG.endpoints.nodeMaxReplicasPerModel(id), {
    method: 'DELETE',
  }),
  listScheduling: () => fetchJSON(API_CONFIG.endpoints.nodesScheduling),
  setScheduling: (config) => postJSON(API_CONFIG.endpoints.nodesScheduling, config),
  deleteScheduling: (model) => fetchJSON(API_CONFIG.endpoints.nodesSchedulingModel(model), { method: 'DELETE' }),
}

// File to base64 helper
export function fileToBase64(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.onload = () => {
      const base64 = reader.result.split(',')[1] || reader.result
      resolve(base64)
    }
    reader.onerror = reject
    reader.readAsDataURL(file)
  })
}
