(() => {
  class LumiRequestError extends Error {
    constructor(message, details = {}) {
      super(message || "The request could not be completed.");
      this.name = "LumiRequestError";
      this.kind = details.kind || "server";
      this.status = details.status || 0;
      this.fieldErrors = details.fieldErrors || {};
      this.data = details.data || {};
    }
  }

  const enhancedHeaders = {
    Accept: "application/json, text/html;q=0.8",
    "X-Lumi-Enhanced": "1",
    "X-Requested-With": "XMLHttpRequest"
  };

  function normalize(payload, response) {
    if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
      return {
        ok: response.ok,
        message: response.ok ? "Saved." : "The request could not be completed.",
        fieldErrors: {},
        data: {},
        patch: {},
        redirect: null,
        reloadRequired: false
      };
    }
    const explicitOk = payload.ok ?? payload.success;
    return {
      ...payload,
      ok: explicitOk === undefined ? response.ok : Boolean(explicitOk),
      message: payload.message || payload.error || (response.ok ? "Saved." : "The request could not be completed."),
      fieldErrors: payload.fieldErrors && typeof payload.fieldErrors === "object" ? payload.fieldErrors : {},
      data: payload.data && typeof payload.data === "object" ? payload.data : {},
      patch: payload.patch && typeof payload.patch === "object" ? payload.patch : {},
      redirect: payload.redirect || null,
      reloadRequired: payload.reloadRequired === true
    };
  }

  async function parseResponse(response) {
    const contentType = String(response.headers.get("content-type") || "").toLowerCase();
    if (contentType.includes("application/json")) {
      return normalize(await response.json().catch(() => null), response);
    }
    const html = await response.text().catch(() => "");
    if (response.ok) return normalize(null, response);
    const parsed = new DOMParser().parseFromString(html, "text/html");
    const message = parsed.querySelector("[role='alert'], .flash.error, .error-message, main h1 + p")?.textContent?.trim();
    return normalize({ ok: false, message: message || `The server returned ${response.status}.` }, response);
  }

  async function request(url, options = {}) {
    const headers = new Headers(options.headers || {});
    Object.entries(enhancedHeaders).forEach(([name, value]) => {
      if (!headers.has(name)) headers.set(name, value);
    });
    try {
      const response = await fetch(url, {
        credentials: "same-origin",
        redirect: "follow",
        ...options,
        headers
      });
      const result = await parseResponse(response);
      if (!response.ok || !result.ok) {
        throw new LumiRequestError(result.message, {
          kind: response.status === 401 || response.status === 403 ? "permission" : response.status === 422 || response.status === 400 ? "validation" : "server",
          status: response.status,
          fieldErrors: result.fieldErrors,
          data: result.data
        });
      }
      return result;
    } catch (error) {
      if (error instanceof LumiRequestError || error?.name === "AbortError") throw error;
      throw new LumiRequestError("Lumi could not be reached. Your changes are still in this page; check the connection and try again.", {
        kind: "network"
      });
    }
  }

  function formRequest(form, options = {}) {
    const submitter = options.submitter || null;
    const method = String(submitter?.getAttribute?.("formmethod") || form.getAttribute("method") || "POST").toUpperCase();
    const url = submitter?.getAttribute?.("formaction") || form.getAttribute("action") || window.location.href;
    const formData = submitter ? new FormData(form, submitter) : new FormData(form);
    const hasFile = Array.from(formData.values()).some((value) => value instanceof File && value.size > 0);
    let body = formData;
    const headers = new Headers(options.headers || {});
    if (!hasFile && method !== "GET") {
      body = new URLSearchParams();
      for (const [name, value] of formData.entries()) {
        if (typeof value === "string") body.append(name, value);
      }
      headers.set("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
    }
    if (method === "GET") {
      const next = new URL(url, window.location.href);
      for (const [name, value] of formData.entries()) {
        if (typeof value === "string") next.searchParams.append(name, value);
      }
      return request(next.href, { ...options, method, body: undefined, headers });
    }
    return request(url, { ...options, method, body, headers });
  }

  window.LumiRequest = Object.freeze({ request, form: formRequest, Error: LumiRequestError });
})();
