// Thin fetch wrapper. Centralized so every call has consistent error handling
// and json parsing. Errors throw an ApiError with the response status + body.

export class ApiError extends Error {
  constructor(message, status, body) {
    super(message)
    this.name = 'ApiError'
    this.status = status
    this.body = body
  }
}

async function request(method, url, { body, params, signal } = {}) {
  let fullUrl = url
  if (params) {
    const search = new URLSearchParams()
    for (const [k, v] of Object.entries(params)) {
      if (v === undefined || v === null) continue
      // Array values serialize as a REPEATED key (k=a&k=b) — e.g. the
      // gallery's tag_or OR-groups, read server-side via request.args.getlist.
      if (Array.isArray(v)) {
        for (const item of v) {
          if (item !== undefined && item !== null) search.append(k, String(item))
        }
      } else {
        search.set(k, String(v))
      }
    }
    const qs = search.toString()
    if (qs) fullUrl += (url.includes('?') ? '&' : '?') + qs
  }

  const headers = {}
  const init = { method, headers, signal }
  if (body !== undefined) {
    if (body instanceof FormData) {
      // Let the browser set Content-Type with the multipart boundary.
      // JSON-stringifying FormData produces "{}" and the backend sees
      // an empty multipart payload (this broke FC-5 IR/GS ingest from
      // the UI for several releases).
      init.body = body
    } else {
      headers['Content-Type'] = 'application/json'
      init.body = JSON.stringify(body)
    }
  }

  const r = await fetch(fullUrl, init)
  const text = await r.text()
  let parsed
  try { parsed = text ? JSON.parse(text) : null } catch { parsed = text }

  if (!r.ok) {
    const msg = (parsed && parsed.error) || `${r.status} ${r.statusText}`
    throw new ApiError(msg, r.status, parsed)
  }
  return parsed
}

export function useApi() {
  return {
    get:    (url, opts) => request('GET',    url, opts),
    post:   (url, opts) => request('POST',   url, opts),
    patch:  (url, opts) => request('PATCH',  url, opts),
    delete: (url, opts) => request('DELETE', url, opts)
  }
}
