Source: background.js

importScripts("config.js");

const browserAPI = typeof browser !== "undefined" ? browser : chrome;

let accessToken = null;
let tokenStoredAt = null;
const MAX_AGE = 8 * 60 * 60 * 1000;

/**
 * It authenticates the user using OAuth2
 * @returns the authentification token
 */

async function getAccessToken() {
  // Check memory with age
  if (accessToken && tokenStoredAt && Date.now() - tokenStoredAt < MAX_AGE) {
    console.log("Token found in memory, still valid");
    return accessToken;
  }

  // Check storage with age
  const stored = await browserAPI.storage.local.get([
    "access_token",
    "token_stored_at",
  ]);
  if (
    stored.access_token &&
    stored.token_stored_at &&
    Date.now() - stored.token_stored_at < MAX_AGE
  ) {
    accessToken = stored.access_token;
    tokenStoredAt = stored.token_stored_at;
    console.log("Token found in storage, still valid");
    return accessToken;
  }

  // Token missing or expired
  accessToken = null;
  tokenStoredAt = null;
  await browserAPI.storage.local.remove(["access_token", "token_stored_at"]);
  console.log("Token expired or missing, launching auth flow...");

  const redirectUri = browserAPI.identity.getRedirectURL();
  const scopes = CONFIG.scopes.join(" ");
  const authUrl =
    "https://accounts.google.com/o/oauth2/v2/auth" +
    "?client_id=" +
    encodeURIComponent(CONFIG.client_id) +
    "&response_type=token" +
    "&redirect_uri=" +
    encodeURIComponent(redirectUri) +
    "&scope=" +
    encodeURIComponent(scopes) +
    "&prompt=login" +
    "&max_age=28800";

  return new Promise((resolve, reject) => {
    browserAPI.identity.launchWebAuthFlow(
      { url: authUrl, interactive: true },
      async (redirectedTo) => {
        if (browserAPI.runtime.lastError) {
          reject(browserAPI.runtime.lastError);
          return;
        }
        const hash = new URL(redirectedTo).hash.substring(1);
        const params = new URLSearchParams(hash);
        const token = params.get("access_token");
        if (!token) {
          reject(new Error("Access token not found"));
          return;
        }
        const now = Date.now();
        accessToken = token;
        tokenStoredAt = now;
        await browserAPI.storage.local.set({
          access_token: token,
          token_stored_at: now,
        });
        console.log("Token stored");
        resolve(accessToken);
      },
    );
  });
}

async function handlerToken() {
  try {
    const token = await getAccessToken();
    console.log(token);
    return token;
  } catch (error) {
    browserAPI.notifications.create(`auth_denied_${Date.now()}`, {
      type: "basic",
      iconUrl: browserAPI.runtime.getURL("icons/error-48.png"),
      title: "MailCategoryManager",
      message: "Access denied. Please try again.",
    });
    return null;
  }
}

async function deleteLabel(labelName) {
  const token = await handlerToken();

  if (!token) return "Authentication failed.";

  const listResponse = await fetch(
    "https://www.googleapis.com/gmail/v1/users/me/labels",
    { headers: { Authorization: `Bearer ${token}` } },
  );

  if (!listResponse.ok) {
    const err = await listResponse.json();
    return `Error fetching labels: ${err.error?.message || listResponse.statusText}`;
  }

  const listData = await listResponse.json();
  const labels = listData.labels || [];

  const toDelete = labels.filter((l) => {
    const normalizedLabel = l.name.replace(/^\//, "");
    const normalizedInput = labelName.replace(/^\//, "");

    // match if label contains the input anywhere in the path
    return (
      normalizedLabel === normalizedInput ||
      normalizedLabel.includes(normalizedInput)
    );
  });

  console.log(toDelete);

  if (toDelete.length === 0) {
    browserAPI.notifications.create(
      `label_error_${Date.now()}`,
      {
        type: "basic",
        iconUrl: browserAPI.runtime.getURL("icons/error-48.png"),
        title: "GmailCategoryManager Error",
        message: `Label '${labelName}' not found!`,
      },
      (id) => {
        console.log("Notification created:", id);
      },
    );
    return `Label '${labelName}' not found.`;
  }

  const results = await Promise.all(
    toDelete.map(async (label) => {
      const deleteResponse = await fetch(
        `https://www.googleapis.com/gmail/v1/users/me/labels/${label.id}`,
        {
          method: "DELETE",
          headers: { Authorization: `Bearer ${token}` },
        },
      );
      if (!deleteResponse.ok) {
        const errorData = await deleteResponse.json();
        return `Failed to delete '${label.name}': ${errorData.error?.message}`;
      }
      return `'${label.name}' deleted`;
    }),
  );

  console.log("Deleted labels:", results);

  browserAPI.notifications.create(`label_deleted_${Date.now()}`, {
    type: "basic",
    iconUrl: browserAPI.runtime.getURL("icons/success-48.png"),
    title: "GmailCategoryManager",
    message: `Deleted ${toDelete.length} label(s) under '${labelName}'`,
  });

  return results.join(", ");
}

async function createLabel(labelName, color) {
  const token = await handlerToken();

  if (!token) return "Authentication failed.";

  try {
    const parts = labelName.split("/");
    let createdLabel;

    for (let i = 0; i < parts.length; i++) {
      const partialPath = parts.slice(0, i + 1).join("/");

      const label = {
        name: partialPath,
        labelListVisibility: "labelShow",
        messageListVisibility: "show",
        color: {
          backgroundColor: color,
          textColor: "#000000",
        },
      };

      const response = await fetch(
        "https://www.googleapis.com/gmail/v1/users/me/labels",
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${token}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify(label),
        },
      );

      // 409 = label already exists, skip it
      if (response.status === 409) continue;

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.error.message);
      }

      createdLabel = await response.json(); // ← saves last created label
    }

    console.log(
      `Label created: ${createdLabel.name} with ID: ${createdLabel.id}`,
    );
    browserAPI.notifications.create(`label_created_${Date.now()}`, {
      type: "basic",
      iconUrl: browserAPI.runtime.getURL("icons/success-48.png"),
      title: "GmailCategoryManager",
      message: `Label created: ${createdLabel.name} with ID: ${createdLabel.id}`,
    });
    return `Label created: ${createdLabel.name} with ID: ${createdLabel.id}`;
  } catch (error) {
    console.error("Error creating label:", error.message);
    browserAPI.notifications.create(`label_created_error_${Date.now()}`, {
      type: "basic",
      iconUrl: browserAPI.runtime.getURL("icons/error-48.png"),
      title: "GmailCategoryManager Error",
      message: `Error creating label: ${error.message}`,
    });
    return `Error creating label: ${error.message}`;
  }
}

async function getLabelStats() {
  const token = await handlerToken();
  if (!token) throw new Error("Authentication failed.");

  const response = await fetch(
    "https://www.googleapis.com/gmail/v1/users/me/labels",
    { headers: { Authorization: `Bearer ${token}` } },
  );
  if (!response.ok) throw new Error("Failed to fetch labels");

  const data = await response.json();
  const labels = data.labels || [];

  const userLabels = labels.filter((l) => l.type === "user");

  return {
    userCount: userLabels.length,
    allLabels: userLabels.map((l) => ({
      name: l.name,
      color: l.color?.backgroundColor || null,
    })),
  };
}

browserAPI.runtime.onMessage.addListener((request, sender, sendResponse) => {
  console.log("Received message:", request);

  if (request.action === "notify_error") {
    browserAPI.notifications.create(`label_error_${Date.now()}`, {
      type: "basic",
      iconUrl: browserAPI.runtime.getURL("icons/error-48.png"),
      title: "GmailCategoryManager Error",
      message: request.message || "An error occurred",
    });

    sendResponse({ success: true });
    return true;
  }

  if (request.action === "createLabel") {
    createLabel(request.labelName, request.color)
      .then((result) => {
        sendResponse({ success: true, result });
      })
      .catch((error) => {
        browserAPI.notifications.create(`label_error_${Date.now()}`, {
          type: "basic",
          iconUrl: browserAPI.runtime.getURL("icons/error-48.png"),
          title: "GmailCategoryManager Error",
          message: error.message || "An error occurred",
        });

        sendResponse({ success: false, error: error.message });
      });

    return true;
  }

  if (request.action === "deleteLabel") {
    deleteLabel(request.labelName)
      .then((result) => {
        sendResponse({ success: true, result });
      })
      .catch((error) => {
        browserAPI.notifications.create(`label_error_${Date.now()}`, {
          type: "basic",
          iconUrl: browserAPI.runtime.getURL("icons/error-48.png"),
          title: "GmailCategoryManager Error",
          message: error.message || "An error occurred",
        });

        sendResponse({ success: false, error: error.message });
      });

    return true;
  }

  if (request.action === "getLabelStats") {
    getLabelStats()
      .then((stats) => {
        sendResponse({ success: true, stats });
      })
      .catch((error) => {
        sendResponse({ success: false, error: error.message });
      });
    return true;
  }
});