async function parseError(res, text) {
  try {
    const j = JSON.parse(text);
    if (j.message) {
      const base = Array.isArray(j.message) ? j.message.join(', ') : j.message;
      const parts = [base];
      if (j.detail && String(j.detail).trim()) {
        parts.push(String(j.detail).trim());
      }
      if (j.hint && String(j.hint).trim()) {
        parts.push(String(j.hint).trim());
      }
      return parts.join(' — ');
    }
  } catch {
    /* ignore */
  }
  return text || res.statusText;
}

function redirectToLogin() {
  const p = location.pathname;
  if (p !== '/login.html' && p !== '/bootstrap.html') {
    location.href = '/login.html';
  }
}

/** Geschützte REST-API liegt unter /api (Root-URLs bleiben für die SPA frei). */
export function apiUrl(path) {
  if (path.startsWith('/auth/')) return path;
  return `/api${path.startsWith('/') ? path : `/${path}`}`;
}

function onUnauthorized(path) {
  if (path.startsWith('/auth/')) return;
  redirectToLogin();
}

/** Wird bei 401 geworfen: Aufrufer sollen keine Fehlerseite rendern. */
export function isAuthRedirectError(e) {
  return Boolean(e && (e.authRedirect === true || e.name === 'AuthRedirect'));
}

function authRedirectError() {
  const err = new Error('SESSION');
  err.name = 'AuthRedirect';
  err.authRedirect = true;
  return err;
}

function parseJsonBody(text) {
  if (!text) return null;
  const trimmed = text.trim();
  if (trimmed.startsWith('<')) {
    throw new Error(
      'Server lieferte HTML statt JSON (API-URL/Proxy prüfen oder Server neu starten).',
    );
  }
  try {
    return JSON.parse(text);
  } catch (e) {
    throw new Error(
      e && String(e.message || e).includes('JSON')
        ? 'Ungültige Server-Antwort (kein JSON).'
        : e.message || 'Ungültige Server-Antwort',
    );
  }
}

async function apiRequest(method, path, body) {
  const url = apiUrl(path);
  const opt = { method, credentials: 'include', headers: {} };
  if (body !== undefined) {
    opt.headers['Content-Type'] = 'application/json';
    opt.body = JSON.stringify(body);
  }
  const res = await fetch(url, opt);
  const text = await res.text();
  if (res.status === 401) {
    if (path.startsWith('/auth/')) {
      throw new Error((await parseError(res, text)) || 'Anmeldung fehlgeschlagen');
    }
    onUnauthorized(path);
    throw authRedirectError();
  }
  if (!res.ok) throw new Error(await parseError(res, text));
  return parseJsonBody(text);
}

export async function apiGet(path) {
  return apiRequest('GET', path);
}

export async function apiPost(path, body) {
  return apiRequest('POST', path, body);
}

export async function apiPut(path, body) {
  return apiRequest('PUT', path, body);
}

export async function apiDelete(path) {
  return apiRequest('DELETE', path);
}

/** multipart/form-data (kein Content-Type setzen — Boundary setzt der Browser) */
export async function apiPostForm(path, formData) {
  const url = apiUrl(path);
  const res = await fetch(url, {
    method: 'POST',
    credentials: 'include',
    body: formData,
  });
  const text = await res.text();
  if (res.status === 401) {
    onUnauthorized(path);
    throw authRedirectError();
  }
  if (!res.ok) throw new Error(await parseError(res, text));
  return parseJsonBody(text);
}

/** Öffentlich: keine Session nötig, kein Redirect bei 401 */
export async function authFetchStatus() {
  const res = await fetch('/auth/status', { credentials: 'include' });
  const text = await res.text();
  if (!res.ok) throw new Error(await parseError(res, text));
  return parseJsonBody(text);
}
