(function exposeApiClient(global) {
  "use strict";

  let csrfTokenSupplier = () => "";
  let unauthorizedHandler = () => {};

  class ApiError extends Error {
    constructor(message, status = 0, payload = null) {
      super(message);
      this.name = "ApiError";
      this.status = status;
      this.payload = payload;
    }
  }

  function configure(options = {}) {
    if (typeof options.csrfToken === "function") csrfTokenSupplier = options.csrfToken;
    if (typeof options.onUnauthorized === "function") unauthorizedHandler = options.onUnauthorized;
  }

  function requestOptions(method, body, signal) {
    const normalizedMethod = String(method || "GET").toUpperCase();
    const options = { method: normalizedMethod, headers: {}, signal };
    const csrfToken = csrfTokenSupplier();
    if (!["GET", "HEAD", "OPTIONS"].includes(normalizedMethod) && csrfToken) {
      options.headers["X-CSRF-Token"] = csrfToken;
    }
    if (body !== null && body !== undefined) {
      options.headers["Content-Type"] = "application/json";
      options.body = JSON.stringify(body);
    }
    return options;
  }

  async function parseJson(response) {
    try {
      return await response.json();
    } catch (_error) {
      return {};
    }
  }

  function handleUnauthorized(response, url) {
    if (response.status === 401 && !String(url).startsWith("/api/auth/")) {
      unauthorizedHandler({ response, url });
    }
  }

  async function request(url, method = "GET", body = null, options = {}) {
    const response = await fetch(url, requestOptions(method, body, options.signal));
    const payload = await parseJson(response);
    handleUnauthorized(response, url);
    if (!response.ok || payload.error) {
      throw new ApiError(payload.error || "请求失败", response.status, payload);
    }
    return payload;
  }

  async function streamNdjson(url, options = {}) {
    const response = await fetch(
      url,
      requestOptions(options.method || "POST", options.body, options.signal),
    );
    if (!response.ok) {
      const payload = await parseJson(response);
      handleUnauthorized(response, url);
      throw new ApiError(
        payload.error || options.errorMessage || "流式请求暂不可用",
        response.status,
        payload,
      );
    }
    if (!response.body) throw new ApiError("当前浏览器不支持流式回答", response.status);
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let buffer = "";
    const consume = (line) => {
      if (!line.trim()) return;
      let event;
      try {
        event = JSON.parse(line);
      } catch (_error) {
        throw new ApiError("流式响应格式错误", response.status);
      }
      if (event.type === "error") {
        throw new ApiError(event.error || options.errorMessage || "流式请求失败", response.status, event);
      }
      options.onEvent?.(event);
    };
    while (true) {
      const { value, done } = await reader.read();
      buffer += decoder.decode(value || new Uint8Array(), { stream: !done });
      const lines = buffer.split("\n");
      buffer = lines.pop() || "";
      lines.forEach(consume);
      if (done) break;
    }
    if (buffer.trim()) consume(buffer);
  }

  global.XiaobaiAPI = Object.freeze({ ApiError, configure, request, streamNdjson });
})(window);
