// api.jsx — общий клиент серверного API.
// Загружается до экранных компонентов и экспортирует window.FL_API.
(() => {
  const AUTH_EVENT = "fl-auth-changed";
  let currentUser = null;
  let sessionLoaded = false;
  let configPromise = null;

  class FLApiError extends Error {
    constructor(message, { status = 0, code = null, details = null, cause = null } = {}) {
      super(message);
      this.name = "FLApiError";
      this.status = status;
      this.code = code;
      this.details = details;
      this.cause = cause;
    }
  }

  const emitAuth = () => {
    window.dispatchEvent(new CustomEvent(AUTH_EVENT, {
      detail: { user: currentUser, loaded: sessionLoaded },
    }));
  };

  const setCurrentUser = (user) => {
    currentUser = user || null;
    sessionLoaded = true;
    emitAuth();
    return currentUser;
  };

  const statusMessage = (status) => {
    if (status === 401) return "Нужно войти в аккаунт";
    if (status === 403) return "У вас нет доступа к этому действию";
    if (status === 404) return "Запрошенный раздел сервера пока недоступен";
    if (status === 409) return "Такая запись уже существует";
    if (status === 429) return "Слишком много запросов. Попробуйте немного позже";
    if (status >= 500) return "Сервер временно недоступен. Попробуйте ещё раз";
    return "Не удалось выполнить запрос";
  };

  async function request(path, options = {}) {
    const requestOptions = { ...options };
    const headers = new Headers(requestOptions.headers || {});
    headers.set("Accept", "application/json");

    const bodyIsObject =
      requestOptions.body != null &&
      typeof requestOptions.body === "object" &&
      !(requestOptions.body instanceof FormData) &&
      !(requestOptions.body instanceof Blob) &&
      !(requestOptions.body instanceof URLSearchParams);

    if (bodyIsObject) {
      headers.set("Content-Type", "application/json");
      requestOptions.body = JSON.stringify(requestOptions.body);
    }

    let response;
    try {
      response = await fetch(path, {
        ...requestOptions,
        credentials: "same-origin",
        headers,
      });
    } catch (cause) {
      throw new FLApiError(
        navigator.onLine
          ? "Не удалось связаться с сервером. Попробуйте ещё раз"
          : "Нет подключения к интернету",
        { cause }
      );
    }

    const contentType = response.headers.get("content-type") || "";
    let payload = null;
    if (response.status !== 204) {
      if (contentType.includes("application/json")) {
        try {
          payload = await response.json();
        } catch {
          payload = null;
        }
      } else {
        const text = await response.text();
        payload =
          text &&
          !contentType.includes("text/html") &&
          text.length <= 500
            ? { error: text }
            : null;
      }
    }

    if (!response.ok) {
      const serverMessage =
        payload && typeof payload === "object" &&
        (payload.error || payload.message);
      throw new FLApiError(
        typeof serverMessage === "string" && serverMessage.trim()
          ? serverMessage
          : statusMessage(response.status),
        {
          status: response.status,
          code: payload?.code || null,
          details: payload?.details || payload,
        }
      );
    }

    return payload;
  }

  const get = (path, options = {}) =>
    request(path, { ...options, method: "GET" });
  const post = (path, body, options = {}) =>
    request(path, { ...options, method: "POST", body });
  const patch = (path, body, options = {}) =>
    request(path, { ...options, method: "PATCH", body });
  const del = (path, options = {}) =>
    request(path, { ...options, method: "DELETE" });

  async function me() {
    const data = await get("/api/auth/me");
    return setCurrentUser(data?.user || null);
  }

  async function requestEmailCode(email) {
    return post("/api/auth/email/request", {
      email: String(email || "").trim().toLowerCase(),
    });
  }

  async function verifyEmailCode(email, code, name) {
    const data = await post("/api/auth/email/verify", {
      email: String(email || "").trim().toLowerCase(),
      code: String(code || "").replace(/\D/g, ""),
      ...(name && String(name).trim() ? { name: String(name).trim() } : {}),
    });
    return setCurrentUser(data?.user || null);
  }

  async function telegramLogin(payload) {
    const data = await post("/api/auth/telegram", payload);
    return setCurrentUser(data?.user || null);
  }

  async function developerLogin(login, password) {
    const data = await post("/api/auth/developer", {
      login: String(login || "").trim(),
      password: String(password || ""),
    });
    return setCurrentUser(data?.user || null);
  }

  async function logout() {
    const data = await post("/api/auth/logout", {});
    setCurrentUser(null);
    try {
      localStorage.removeItem("fl-favs");
      localStorage.removeItem("fl-favs-data");
      window.dispatchEvent(new Event("fl-favs-changed"));
    } catch {}
    return data;
  }

  async function config() {
    if (!configPromise) {
      configPromise = get("/api/config").catch((error) => {
        // Старый сервер может ещё не иметь публичного config-метода.
        if (error instanceof FLApiError && error.status === 404) return {};
        return {};
      });
    }
    return configPromise;
  }

  const normalizeTelegramUsername = (value) => {
    const username = String(value || "").trim().replace(/^@/, "");
    return /^[A-Za-z0-9_]{5,32}$/.test(username) ? username : null;
  };

  async function telegramBotUsername() {
    const publicConfig = await config();
    return normalizeTelegramUsername(
      publicConfig?.telegramBotUsername ||
      publicConfig?.telegram_bot_username ||
      publicConfig?.botUsername ||
      publicConfig?.telegram?.botUsername ||
      window.FL_TELEGRAM_BOT_USERNAME
    );
  }

  async function mountTelegramLogin(container, onAuth, options = {}) {
    if (!container) return { configured: false, destroy() {} };
    const botUsername = await telegramBotUsername();
    if (!botUsername) return { configured: false, destroy() {} };

    const callbackName =
      "FLTelegramAuth_" + Date.now().toString(36) +
      Math.random().toString(36).slice(2);
    let active = true;
    window[callbackName] = (telegramUser) => {
      if (active) onAuth(telegramUser);
    };

    const script = document.createElement("script");
    script.async = true;
    script.src = "https://telegram.org/js/telegram-widget.js?22";
    script.dataset.telegramLogin = botUsername;
    script.dataset.size = options.size || "large";
    script.dataset.radius = options.radius || "12";
    script.dataset.userpic = options.userpic === true ? "true" : "false";
    script.dataset.requestAccess = "write";
    script.dataset.onauth = `${callbackName}(user)`;
    container.replaceChildren(script);

    return {
      configured: true,
      botUsername,
      destroy() {
        active = false;
        if (window[callbackName]) delete window[callbackName];
        if (container.isConnected) container.replaceChildren();
      },
    };
  }

  Object.assign(window, {
    FL_API: {
      FLApiError,
      request,
      get,
      post,
      patch,
      delete: del,
      me,
      requestEmailCode,
      verifyEmailCode,
      telegramLogin,
      developerLogin,
      logout,
      config,
      telegramBotUsername,
      mountTelegramLogin,
      setCurrentUser,
      get currentUser() { return currentUser; },
      get sessionLoaded() { return sessionLoaded; },
      get isAuthenticated() { return !!currentUser; },
      authEvent: AUTH_EVENT,
    },
  });
})();
