const layoutBaseUrl = "https://get-nechto.ru/pl/layout/";
const layoutIds = ["75089", "56661", "55998", "31203"];
const trackedApiOrigin = "https://ncht.bz";
const notifyEndpoint = `${trackedApiOrigin}/api/oko/notify-error`;

function getCurrentPageUrl() {
  try {
    return window.location.href;
  } catch (err) {
    return null;
  }
}

function getCurrentUserId() {
  try {
    return window.accountUserId ?? null;
  } catch (err) {
    return null;
  }
}
function isTrackedUrl(url) {
  return typeof url === "string" && url.startsWith(trackedApiOrigin);
}

function safeSerializeBody(body) {
  if (body == null) return null;

  const limit = 2000;
  const clamp = (value) =>
    typeof value === "string" && value.length > limit
      ? `${value.slice(0, limit)}…`
      : value;

  if (typeof body === "string") {
    return clamp(body);
  }

  if (body instanceof URLSearchParams) {
    return clamp(body.toString());
  }

  if (typeof FormData !== "undefined" && body instanceof FormData) {
    const bag = {};
    body.forEach((value, key) => {
      bag[key] = value instanceof File ? `[file:${value.name}]` : value;
    });
    return bag;
  }

  if (body instanceof Blob) {
    return `[blob size=${body.size} type=${body.type || "unknown"}]`;
  }

  if (body instanceof ArrayBuffer) {
    return `[arraybuffer bytes=${body.byteLength}]`;
  }

  if (ArrayBuffer.isView(body)) {
    return `[typedarray bytes=${body.byteLength}]`;
  }

  if (typeof body === "object") {
    try {
      return clamp(JSON.stringify(body));
    } catch (err) {
      return `[unserializable object: ${err.message}]`;
    }
  }

  return clamp(String(body));
}

function postNotify(payload, transport) {
  try {
    const pageUrl = getCurrentPageUrl();
    const userId = getCurrentUserId();
    const extras = {};
    if (pageUrl && !("pageUrl" in payload)) {
      extras.pageUrl = pageUrl;
    }
    if (userId && !("userId" in payload)) {
      extras.userId = userId;
    }
    const enrichedPayload =
      Object.keys(extras).length > 0 ? { ...payload, ...extras } : payload;
    (transport || window.fetch)(notifyEndpoint, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(enrichedPayload),
      keepalive: true
    });
  } catch (err) {
    // игнорируем сбои при отправке уведомления
  }
}

// Глобальная функция для ручной отправки уведомлений
window.okoNotify = function(type, data) {
  const basePayload = {
    type: type,
    timestamp: new Date().toISOString()
  };

  if (type === "script_error") {
    postNotify({
      ...basePayload,
      message: data.message || "Manual script error",
      source: data.source || window.location.href,
      lineno: data.lineno || null,
      colno: data.colno || null,
      stack: data.stack || null
    });
  } else if (type === "fetch_error") {
    postNotify({
      ...basePayload,
      source: data.source || data.url || window.location.href,
      payload: {
        url: data.url || data.source || window.location.href,
        method: data.method || "GET",
        status: data.status || null,
        ok: data.ok !== undefined ? data.ok : false,
        duration: data.duration || null,
        body: data.body || null,
        error: data.error || null
      }
    });
  } else if (type === "xhr_error") {
    postNotify({
      ...basePayload,
      source: data.source || data.url || window.location.href,
      payload: {
        method: data.method || "GET",
        url: data.url || data.source || window.location.href,
        startedAt: data.startedAt || null,
        body: data.body || null,
        status: data.status || null,
        duration: data.duration || null,
        type: data.type || "error",
        error: data.error || null
      }
    });
  } else {
    // Кастомный тип - передаём как есть
    postNotify({
      ...basePayload,
      ...data
    });
  }
};

window.onerror = function(message, source, lineno, colno, error) {
  // Проверяем, что ошибка из нужных скриптов
  if (
    source &&
    layoutIds.some((id) => source.startsWith(`${layoutBaseUrl}${id}`))
  ) {
    // Формируем данные об ошибке
    const errorData = {
      type: "script_error",
      message,
      source,
      lineno,
      colno,
      stack: error ? error.stack : null,
      timestamp: new Date().toISOString(),
      pageUrl: getCurrentPageUrl(),
      userId: getCurrentUserId()
    };
    // Отправляем уведомление на сервер
    fetch(notifyEndpoint, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(errorData),
      keepalive: true
    });
  }
  // Можно вернуть false, чтобы сообщение об ошибке также выводилось в консоль
  return false;
};


(function setupGlobalApiHooks() {
  if (!window.__apiHooked) {
    window.__apiHooked = true;

    // fetch
    const originalFetch = window.fetch;
    window.fetch = async function hookedFetch(input, init) {
      const startedAt = performance.now();
      try {
        const response = await originalFetch.call(this, input, init);
        logFetch(input, init, response, null, performance.now() - startedAt);
        return response;
      } catch (err) {
        logFetch(input, init, null, err, performance.now() - startedAt);
        throw err;
      }
    };

    function resolveFetchBody(input, init) {
      if (init && "body" in init && init.body !== undefined) {
        return safeSerializeBody(init.body);
      }
      if (
        typeof Request !== "undefined" &&
        input instanceof Request &&
        !input.bodyUsed
      ) {
        return "[stream body]";
      }
      return null;
    }

    function logFetch(input, init, response, error, duration) {
      const meta = {
        url: typeof input === "string" ? input : input.url,
        method:
          init?.method || (typeof input === "object" && input.method) || "GET",
        status: response?.status ?? null,
        ok: response?.ok ?? null,
        duration,
        body: resolveFetchBody(input, init),
        error: error ? { message: error.message, stack: error.stack } : null
      };
      if (!isTrackedUrl(meta.url)) {
        return;
      }

      window.dispatchEvent(new CustomEvent("api:fetch", { detail: meta }));

      const targetUrl = meta.url || "";
      const shouldNotify =
        targetUrl.indexOf(notifyEndpoint) === -1 &&
        (error || (response && response.ok === false));
      if (shouldNotify) {
        postNotify(
          {
            type: "fetch_error",
            source: targetUrl,
            payload: meta,
            timestamp: new Date().toISOString()
          },
          originalFetch
        );
      }
    }

    // XHR
    const OriginalXHR = window.XMLHttpRequest;
    function HookedXHR() {
      const xhr = new OriginalXHR();
      const meta = { method: null, url: null, startedAt: null, body: null };

      const finalize = (type, error) => {
        const detail = {
          ...meta,
          status: xhr.status || null,
          duration: meta.startedAt ? performance.now() - meta.startedAt : null,
          type,
          error: error ? { message: error.message } : null,
        };
        if (!isTrackedUrl(meta.url)) {
          return;
        }

        window.dispatchEvent(new CustomEvent("api:xhr", { detail }));

        if (
          (type !== "load" || (xhr.status && xhr.status >= 400)) &&
          meta.url &&
          meta.url.indexOf(notifyEndpoint) === -1
        ) {
          postNotify(
            {
              type: "xhr_error",
              source: meta.url,
              payload: detail,
              timestamp: new Date().toISOString()
            },
            originalFetch
          );
        }
      };

      xhr.addEventListener("load", () => finalize("load"));
      xhr.addEventListener("error", () =>
        finalize("error", new Error("XHR error"))
      );
      xhr.addEventListener("timeout", () =>
        finalize("timeout", new Error("XHR timeout"))
      );
      xhr.addEventListener("abort", () =>
        finalize("abort", new Error("XHR abort"))
      );

      const originalOpen = xhr.open;
      xhr.open = function patchedOpen(method, url, async, user, password) {
        meta.method = method;
        meta.url = url;
        return originalOpen.call(xhr, method, url, async, user, password);
      };

      const originalSend = xhr.send;
      xhr.send = function patchedSend(body) {
        meta.startedAt = performance.now();
        meta.body = safeSerializeBody(body);
        return originalSend.call(xhr, body);
      };

      return xhr;
    }
    window.XMLHttpRequest = HookedXHR;

    // unhandled rejections
    window.addEventListener("unhandledrejection", (event) => {
      const detail = {
        reason: event.reason,
        stack: event.reason?.stack,
        timestamp: Date.now()
      };
      window.dispatchEvent(
        new CustomEvent("api:unhandled-rejection", { detail })
      );
    });
  }
})();