import { isFetchAllowed } from "/js/plugins/pluginPermissions.js";

const ALLOWED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"];

const FORBIDDEN_HEADERS = ["authorization", "cookie"];
const MAX_BODY_CHARS = 1_000_000;

export async function pluginFetch(
  plugin,
  url,
  init,
  fetchImpl = fetch.bind(globalThis),
) {
  if (!isFetchAllowed(url, plugin.permissions)) {
    throw new Error(`fetch to "${url}" not permitted`);
  }
  const response = await fetchImpl(url, {
    ...sanitizeFetchInit(init),
    credentials: "omit",
    redirect: "error",
    mode: "cors",
    referrerPolicy: "no-referrer",
  });
  const bodyText = await response.text();
  return {
    status: response.status,
    ok: response.ok,
    headers: filterResponseHeaders(response.headers, ["content-type"]),
    body: bodyText,
  };
}

function sanitizeFetchInit(init) {
  const safeInit = {};
  const method = (init?.method ?? "GET").toUpperCase();
  if (!ALLOWED_METHODS.includes(method)) {
    throw new Error(`fetch method "${method}" not permitted`);
  }
  safeInit.method = method;
  const headers = {};
  for (const [name, value] of Object.entries(init?.headers ?? {})) {
    const lowerName = String(name).toLowerCase();
    if (FORBIDDEN_HEADERS.includes(lowerName)) {
      throw new Error(`fetch header "${name}" not permitted`);
    }
    headers[name] = String(value);
  }
  safeInit.headers = headers;
  if (init?.body != null) {
    if (typeof init.body !== "string") {
      throw new Error("fetch body must be a string");
    }
    if (init.body.length > MAX_BODY_CHARS) {
      throw new Error("fetch body too large");
    }
    safeInit.body = init.body;
  }
  return safeInit;
}

function filterResponseHeaders(headers, allowedNames) {
  const picked = {};
  for (const name of allowedNames) {
    const value = headers.get(name);
    if (value != null) picked[name] = value;
  }
  return picked;
}
