// @ts-nocheck
// import { encryptAndPublishSymmetricKeyGroupChat } from "./backgroundFunctions/encryption";

import './qortalRequests'
import { constant, isArray } from "lodash";
import {
  decryptGroupEncryption,
  encryptAndPublishSymmetricKeyGroupChat,
  encryptAndPublishSymmetricKeyGroupChatForAdmins,
  publishGroupEncryptedResource,
  publishOnQDN,
  uint8ArrayToObject,
} from "./backgroundFunctions/encryption";
import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from "./constants/codes";
import { QORT_DECIMALS } from "./constants/constants";
import Base58 from "./deps/Base58";
import {
  base64ToUint8Array,
  decryptSingle,
  encryptSingle,
  objectToBase64,
} from "./qdn/encryption/group-encryption";
import { reusableGet } from "./qdn/publish/pubish";
import { signChat } from "./transactions/signChat";
import { createTransaction } from "./transactions/transactions";
import { decryptChatMessage } from "./utils/decryptChatMessage";
import { decryptStoredWallet } from "./utils/decryptWallet";
import PhraseWallet from "./utils/generateWallet/phrase-wallet";
import { RequestQueueWithPromise } from "./utils/queue/queue";
import { validateAddress } from "./utils/validateAddress";
import { Sha256 } from "asmcrypto.js";
import { TradeBotRespondMultipleRequest } from "./transactions/TradeBotRespondMultipleRequest";
import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from "./constants/resourceTypes";
import TradeBotRespondRequest from './transactions/TradeBotRespondRequest';
import { createRewardShareCase, getRewardSharePrivateKeyCase, listActionsCase, removeRewardShareCase } from './background-cases';




export function cleanUrl(url) {
  return url?.replace(/^(https?:\/\/)?(www\.)?/, '');
}
export function getProtocol(url) {
  if (url?.startsWith('https://')) {
    return 'https';
  } else if (url?.startsWith('http://')) {
    return 'http';
  } else {
    return 'unknown'; // If neither protocol is present
  }
}

export let groupSecretkeys = {}


export const gateways = ['ext-node.qortal.link']


let lastGroupNotification;
export const groupApi = "https://ext-node.qortal.link";
export const groupApiSocket = "wss://ext-node.qortal.link";
export const groupApiLocal = "http://127.0.0.1:12391";
export const groupApiSocketLocal = "ws://127.0.0.1:12391";
const timeDifferenceForNotificationChatsBackground = 600000;
const requestQueueAnnouncements = new RequestQueueWithPromise(1);
let isMobile = false;


const isMobileDevice = () => {
  const userAgent = navigator.userAgent || navigator.vendor || window.opera;

  if (/android/i.test(userAgent)) {
    return true; // Android device
  }

  if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
    return true; // iOS device
  }

  return false;
};

if (isMobileDevice()) {
  isMobile = true;
  console.log("Running on a mobile device");
} else {
  console.log("Running on a desktop");
}
const allQueues = {
  requestQueueAnnouncements: requestQueueAnnouncements,
};

const controlAllQueues = (action) => {
  Object.keys(allQueues).forEach((key) => {
    const val = allQueues[key];
    try {
      if (typeof val[action] === "function") {
        val[action]();
      }
    } catch (error) {
      console.error(error);
    }
  });
};

export const clearAllQueues = () => {
  Object.keys(allQueues).forEach((key) => {
    const val = allQueues[key];
    try {
      val.clear();
    } catch (error) {
      console.error(error);
    }
  });
};

export const getForeignKey = async (foreignBlockchain)=> {
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  switch (foreignBlockchain) {
    case "LITECOIN":
      return parsedData.ltcPrivateKey

      default:
        return null
  }
}

const pauseAllQueues = () => controlAllQueues("pause");
const resumeAllQueues = () => controlAllQueues("resume");
export const checkDifference = (createdTimestamp, diff = timeDifferenceForNotificationChatsBackground) => {
  return (
    Date.now() - createdTimestamp < diff
  );
};
export const getApiKeyFromStorage = async () => {
  return new Promise((resolve, reject) => {
    chrome.storage.local.get("apiKey", (result) => {
      if (chrome.runtime.lastError) {
        return reject(chrome.runtime.lastError);
      }
      resolve(result.apiKey || null); // Return null if apiKey isn't found
    });
  });
};

const getCustomNodesFromStorage = async () => {
  return new Promise((resolve, reject) => {
    chrome.storage.local.get("customNodes", (result) => {
      if (chrome.runtime.lastError) {
        return reject(chrome.runtime.lastError);
      }
      resolve(result.customNodes || null); // Return null if apiKey isn't found
    });
  });
};

// const getArbitraryEndpoint = ()=> {
//   const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously
//   if (apiKey) {
//     return `/arbitrary/resources/search`;
//   } else {
//     return `/arbitrary/resources/searchsimple`;
//   }
// }
export const getArbitraryEndpoint = async () => {
  const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously
  if (apiKey) {
    return `/arbitrary/resources/searchsimple`;
  } else {
    return `/arbitrary/resources/searchsimple`;
  }
};

export const getBaseApi = async (customApi?: string) => {
  if (customApi) {
    return customApi;
  }

  const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously
  if (apiKey) {
    return apiKey?.url;
  } else {
    return groupApi;
  }
};
export const isUsingLocal = async () => {

  const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously
  if (apiKey) {
    return true
  } else {
    return false;
  }
};




export const createEndpoint = async (endpoint, customApi?: string) => {
  if (customApi) {
    return `${customApi}${endpoint}`;
  }

  const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously

  if (apiKey) {
    // Check if the endpoint already contains a query string
    const separator = endpoint.includes("?") ? "&" : "?";
    return `${apiKey?.url}${endpoint}${separator}apiKey=${apiKey?.apikey}`;
  } else {
    return `${groupApi}${endpoint}`;
  }
};

export const walletVersion = 2;
// List of your API endpoints
const apiEndpoints = [
  "https://api.qortal.org",
  "https://api2.qortal.org",
  "https://appnode.qortal.org",
  "https://apinode.qortalnodes.live",
  "https://apinode1.qortalnodes.live",
  "https://apinode2.qortalnodes.live",
  "https://apinode3.qortalnodes.live",
  "https://apinode4.qortalnodes.live",
];

const buyTradeNodeBaseUrl = "https://appnode.qortal.org";
const proxyAccountAddress = "QXPejUe5Za1KD3zCMViWCX35AreMQ9H7ku";
const proxyAccountPublicKey = "5hP6stDWybojoDw5t8z9D51nV945oMPX7qBd29rhX1G7";
const pendingResponses = new Map();
let groups = null;

let socket;
let timeoutId;
let groupSocketTimeout;
let socketTimeout: any;
let interval;
let intervalThreads;
// Function to check each API endpoint
export async function findUsableApi() {
  for (const endpoint of apiEndpoints) {
    try {
      const response = await fetch(`${endpoint}/admin/status`);
      if (!response.ok) throw new Error("Failed to fetch");

      const data = await response.json();
      if (data.isSynchronizing === false && data.syncPercent === 100) {
        console.log(`Usable API found: ${endpoint}`);
        return endpoint;
      } else {
        console.log(`API not ready: ${endpoint}`);
      }
    } catch (error) {
      console.error(`Error checking API ${endpoint}:`, error);
    }
  }

  throw new Error("No usable API found");
}

export function isExtMsg(data) {
  let isMsgFromExtensionGroup = true;
  try {
    const decode1 = atob(data);
    const decode2 = atob(decode1);
    const keyStr = decode2.slice(0, 10);

    // Convert the key string back to a number
    const highestKey = parseInt(keyStr, 10);
    if (isNaN(highestKey)) {
      isMsgFromExtensionGroup = false;
    }
  } catch (error) {
    isMsgFromExtensionGroup = false;
  }

  return isMsgFromExtensionGroup;
}

export function isUpdateMsg(data) {
  let isUpdateMessage = true;
  try {
    const decode1 = atob(data);
    const decode2 = atob(decode1);
    const keyStr = decode2.slice(10, 13);

    // Convert the key string back to a number
    const numberKey = parseInt(keyStr, 10);
    if (isNaN(numberKey)) {
      isUpdateMessage = false;
    } else if(numberKey !== RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS){
      isUpdateMessage = false;
    }
  } catch (error) {
    isUpdateMessage = false;
  }

  return isUpdateMessage;
}

async function checkWebviewFocus() {
  return new Promise((resolve) => {
    // Set a timeout for 1 second
    const timeout = setTimeout(() => {
      resolve(false); // No response within 1 second, assume not focused
    }, 1000);

    // Send message to the content script to check focus
    chrome.runtime.sendMessage({ action: "CHECK_FOCUS" }, (response) => {
      clearTimeout(timeout); // Clear the timeout if we get a response

      if (chrome.runtime.lastError) {
        resolve(false); // Error occurred, assume not focused
      } else {
        resolve(response); // Resolve based on the response
      }
    });
  });
}

function playNotificationSound() {
  // chrome.runtime.sendMessage({ action: "PLAY_NOTIFICATION_SOUND" });
}

const handleNotificationDirect = async (directs) => {
  let isFocused;
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  let isDisableNotifications = await getUserSettings({key: 'disable-push-notifications'}) || false
  const dataDirects = directs.filter((direct) => direct?.sender !== address);
  try {
    if(isDisableNotifications) return
    if (!dataDirects || dataDirects?.length === 0) return;
    isFocused = await checkWebviewFocus();

    if (isFocused) {
      throw new Error("isFocused");
    }
    const newActiveChats = dataDirects;
    const oldActiveChats = await getChatHeadsDirect();

    if (newActiveChats?.length === 0) return;

    let newestLatestTimestamp;
    let oldestLatestTimestamp;
    // Find the latest timestamp from newActiveChats
    newActiveChats?.forEach((newChat) => {
      if (
        !newestLatestTimestamp ||
        newChat?.timestamp > newestLatestTimestamp?.timestamp
      ) {
        newestLatestTimestamp = newChat;
      }
    });

    // Find the latest timestamp from oldActiveChats
    oldActiveChats?.forEach((oldChat) => {
      if (
        !oldestLatestTimestamp ||
        oldChat?.timestamp > oldestLatestTimestamp?.timestamp
      ) {
        oldestLatestTimestamp = oldChat;
      }
    });

    if (
      (checkDifference(newestLatestTimestamp.timestamp) &&
        !oldestLatestTimestamp) ||
      (newestLatestTimestamp &&
        newestLatestTimestamp?.timestamp > oldestLatestTimestamp?.timestamp)
    ) {
      const notificationId =
        "chat_notification_" +
        Date.now() +
        "_type=direct" +
        `_from=${newestLatestTimestamp.address}`;
      chrome.notifications.create(notificationId, {
        type: "basic",
        iconUrl: "qort.png", // Add an appropriate icon for chat notifications
        title: `New Direct message! ${
          newestLatestTimestamp?.name && `from ${newestLatestTimestamp.name}`
        }`,
        message: "You have received a new direct message",
        priority: 2, // Use the maximum priority to ensure it's noticeable
        // buttons: [
        //   { title: 'Go to group' }
        // ]
      });
      if (!isMobile) {
        setTimeout(() => {
          chrome.notifications.clear(notificationId);
        }, 7000);
      }

      // chrome.runtime.sendMessage(
      //   {
      //     action: "notification",
      //     payload: {
      //     },
      //   }
      // )
      // audio.play();
      playNotificationSound();
    }
  } catch (error) {
    if (!isFocused) {
      chrome.runtime.sendMessage(
        {
          action: "notification",
          payload: {},
        },
        (response) => {
          if (!response?.error) {
          }
        }
      );
      const notificationId = "chat_notification_" + Date.now();
      chrome.notifications.create(notificationId, {
        type: "basic",
        iconUrl: "qort.png", // Add an appropriate icon for chat notifications
        title: `New Direct message!`,
        message: "You have received a new direct message",
        priority: 2, // Use the maximum priority to ensure it's noticeable
        // buttons: [
        //   { title: 'Go to group' }
        // ]
      });
      if (!isMobile) {
        setTimeout(() => {
          chrome.notifications.clear(notificationId);
        }, 7000);
      }
      playNotificationSound();
      // audio.play();
      // }
    }
  } finally {
    setChatHeadsDirect(dataDirects);
    // chrome.runtime.sendMessage(
    //   {
    //     action: "setChatHeads",
    //     payload: {
    //       data,
    //     },
    //   }
    // );
  }
};
async function getThreadActivity() {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const key = `threadactivity-${address}`;
  const res = await chrome.storage.local.get([key]);
  if (res?.[key]) {
    const parsedData = JSON.parse(res[key]);
    return parsedData;
  } else {
    return null;
  }
}

async function updateThreadActivity({ threadId, qortalName, groupId, thread }) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const ONE_WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000; // One week in milliseconds
  let lastResetTime = 0;
  // Retrieve the last reset timestamp from storage
  const key = `threadactivity-${address}`;

  chrome.storage.local.get([key], (data) => {
    let threads;

    if (!data[key] || Object.keys(data?.[key]?.length === 0)) {
      threads = {
        createdThreads: [],
        mostVisitedThreads: [],
        recentThreads: [],
      };
    } else {
      threads = JSON.parse(data[key]);
    }
    if (threads?.lastResetTime) {
      lastResetTime = threads.lastResetTime;
    }

    const currentTime = Date.now();

    // Check if a week has passed since the last reset
    if (!lastResetTime || currentTime - lastResetTime > ONE_WEEK_IN_MS) {
      // Reset the visit counts for all most visited threads
      threads.mostVisitedThreads.forEach((thread) => (thread.visitCount = 0));
      lastResetTime = currentTime; // Update the last reset time
      threads.lastResetTime = lastResetTime;
    }

    // Update the recent threads list
    threads.recentThreads = threads.recentThreads.filter(
      (t) => t.threadId !== threadId
    );
    threads.recentThreads.unshift({
      threadId,
      qortalName,
      groupId,
      thread,
      visitCount: 1,
      lastVisited: Date.now(),
    });

    // Sort the recent threads by lastVisited time (descending)
    threads.recentThreads.sort((a, b) => b.lastVisited - a.lastVisited);
    // Limit the recent threads list to 2 items
    threads.recentThreads = threads.recentThreads.slice(0, 2);

    // Update the most visited threads list
    const existingThread = threads.mostVisitedThreads.find(
      (t) => t.threadId === threadId
    );
    if (existingThread) {
      existingThread.visitCount += 1;
      existingThread.lastVisited = Date.now(); // Update the last visited time as well
    } else {
      threads.mostVisitedThreads.push({
        threadId,
        qortalName,
        groupId,
        thread,
        visitCount: 1,
        lastVisited: Date.now(),
      });
    }

    // Sort the most visited threads by visitCount (descending)
    threads.mostVisitedThreads.sort((a, b) => b.visitCount - a.visitCount);
    // Limit the most visited threads list to 2 items
    threads.mostVisitedThreads = threads.mostVisitedThreads.slice(0, 2);

    // Store the updated thread information and last reset time
    // chrome.storage.local.set({ threads, lastResetTime });

    const dataString = JSON.stringify(threads);
    chrome.storage.local.set({ [`threadactivity-${address}`]: dataString });
  });
}

const handleNotification = async (groups) => {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  let isDisableNotifications = await getUserSettings({key: 'disable-push-notifications'}) || false

  let mutedGroups = await getUserSettings({key: 'mutedGroups'}) || []
  if(!isArray(mutedGroups)) mutedGroups = []
  mutedGroups.push('0')
  
  let isFocused;
  const data = groups.filter((group) => group?.sender !== address && !mutedGroups.includes(group.groupId) && !isUpdateMsg(group?.data));
  const dataWithUpdates = groups.filter((group) => group?.sender !== address && !mutedGroups.includes(group.groupId));

  try {
    if(isDisableNotifications) return
    if (!data || data?.length === 0) return;
    isFocused = await checkWebviewFocus();

    if (isFocused) {
      throw new Error("isFocused");
    }
    const newActiveChats = data;
    const oldActiveChats = await getChatHeads();

    let results = [];
    let newestLatestTimestamp;
    let oldestLatestTimestamp;
    // Find the latest timestamp from newActiveChats
    newActiveChats?.forEach((newChat) => {
      if (
        !newestLatestTimestamp ||
        newChat?.timestamp > newestLatestTimestamp?.timestamp
      ) {
        newestLatestTimestamp = newChat;
      }
    });

    // Find the latest timestamp from oldActiveChats
    oldActiveChats?.forEach((oldChat) => {
      if (
        !oldestLatestTimestamp ||
        oldChat?.timestamp > oldestLatestTimestamp?.timestamp
      ) {
        oldestLatestTimestamp = oldChat;
      }
    });

    if (
      (checkDifference(newestLatestTimestamp.timestamp) &&
        !oldestLatestTimestamp) ||
      (newestLatestTimestamp &&
        newestLatestTimestamp?.timestamp > oldestLatestTimestamp?.timestamp)
    ) {
      if (
        !lastGroupNotification ||
        Date.now() - lastGroupNotification >= 120000
      ) {
        if (
          !newestLatestTimestamp?.data ||
          !isExtMsg(newestLatestTimestamp?.data)
        )
          return;

        const notificationId =
          "chat_notification_" +
          Date.now() +
          "_type=group" +
          `_from=${newestLatestTimestamp.groupId}`;

        chrome.notifications.create(notificationId, {
          type: "basic",
          iconUrl: "qort.png", // Add an appropriate icon for chat notifications
          title: "New Group Message!",
          message: `You have received a new message from ${newestLatestTimestamp?.groupName}`,
          priority: 2, // Use the maximum priority to ensure it's noticeable
          // buttons: [
          //   { title: 'Go to group' }
          // ]
        });
        if (!isMobile) {
          setTimeout(() => {
            chrome.notifications.clear(notificationId);
          }, 7000);
        }
        // chrome.runtime.sendMessage(
        //   {
        //     action: "notification",
        //     payload: {
        //     },
        //   }
        // )
        // audio.play();
        playNotificationSound();
        lastGroupNotification = Date.now();
      }
    }
  } catch (error) {
    if (!isFocused) {
      chrome.runtime.sendMessage(
        {
          action: "notification",
          payload: {},
        },
        (response) => {
          if (!response?.error) {
          }
        }
      );
      const notificationId = "chat_notification_" + Date.now();
      chrome.notifications.create(notificationId, {
        type: "basic",
        iconUrl: "qort.png", // Add an appropriate icon for chat notifications
        title: "New Group Message!",
        message: "You have received a new message from one of your groups",
        priority: 2, // Use the maximum priority to ensure it's noticeable
        // buttons: [
        //   { title: 'Go to group' }
        // ]
      });
      if (!isMobile) {
        setTimeout(() => {
          chrome.notifications.clear(notificationId);
        }, 7000);
      }
      playNotificationSound();
      // audio.play();
      lastGroupNotification = Date.now();
      // }
    }
  } finally {
    if (!data || data?.length === 0) return;
    setChatHeads(dataWithUpdates);
    // chrome.runtime.sendMessage(
    //   {
    //     action: "setChatHeads",
    //     payload: {
    //       data,
    //     },
    //   }
    // );
  }
};

const checkThreads = async (bringBack) => {
  try {
    let myName = "";
    const userData = await getUserInfo();
    if (userData?.name) {
      myName = userData.name;
    }
    let newAnnouncements = [];
    let dataToBringBack = [];
    const threadActivity = await getThreadActivity();
    if (!threadActivity) return null;

    const selectedThreads = [
      ...threadActivity.createdThreads.slice(0, 2),
      ...threadActivity.mostVisitedThreads.slice(0, 2),
      ...threadActivity.recentThreads.slice(0, 2),
    ];

    if (selectedThreads?.length === 0) return null;
    const tempData = {};
    for (const thread of selectedThreads) {
      try {
        const identifier = `thmsg-${thread?.threadId}`;
        const name = thread?.qortalName;
        const endpoint = await getArbitraryEndpoint();
        const url = await createEndpoint(
          `${endpoint}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=1&includemetadata=false&offset=${0}&reverse=true&prefix=true`
        );
        const response = await fetch(url, {
          method: "GET",
          headers: {
            "Content-Type": "application/json",
          },
        });
        const responseData = await response.json();

        const latestMessage = responseData.filter(
          (pub) => pub?.name !== myName
        )[0];
        // const latestMessage = responseData[0]

        if (!latestMessage) {
          continue;
        }

        if (
          checkDifference(latestMessage.created) &&
          latestMessage.created > thread?.lastVisited &&
          (!thread?.lastNotified || thread?.lastNotified < thread?.created)
        ) {
          tempData[thread.threadId] = latestMessage.created;
          newAnnouncements.push(thread);
        }
        if (latestMessage.created > thread?.lastVisited) {
          dataToBringBack.push(thread);
        }
      } catch (error) {
        conosle.log({ error });
      }
    }

    if (bringBack) {
      return dataToBringBack;
    }

    const updateThreadWithLastNotified = {
      ...threadActivity,
      createdThreads: (threadActivity?.createdThreads || [])?.map((item) => {
        if (tempData[item.threadId]) {
          return {
            ...item,
            lastNotified: tempData[item.threadId],
          };
        } else {
          return item;
        }
      }),
      mostVisitedThreads: (threadActivity?.mostVisitedThreads || [])?.map(
        (item) => {
          if (tempData[item.threadId]) {
            return {
              ...item,
              lastNotified: tempData[item.threadId],
            };
          } else {
            return item;
          }
        }
      ),
      recentThreads: (threadActivity?.recentThreads || [])?.map((item) => {
        if (tempData[item.threadId]) {
          return {
            ...item,
            lastNotified: tempData[item.threadId],
          };
        } else {
          return item;
        }
      }),
    };

    const wallet = await getSaveWallet();
    const address = wallet.address0;
    const dataString = JSON.stringify(updateThreadWithLastNotified);
    chrome.storage.local.set({ [`threadactivity-${address}`]: dataString });

    if (newAnnouncements.length > 0) {
      const notificationId =
        "chat_notification_" +
        Date.now() +
        "_type=thread-post" +
        `_data=${JSON.stringify(newAnnouncements[0])}`;
        let isDisableNotifications = await getUserSettings({key: 'disable-push-notifications'}) || false
      if(!isDisableNotifications){
        chrome.notifications.create(notificationId, {
          type: "basic",
          iconUrl: "qort.png", // Add an appropriate icon for chat notifications
          title: `New thread post!`,
          message: `New post in ${newAnnouncements[0]?.thread?.threadData?.title}`,
          priority: 2, // Use the maximum priority to ensure it's noticeable
          // buttons: [
          //   { title: 'Go to group' }
          // ]
        });
        if (!isMobile) {
          setTimeout(() => {
            chrome.notifications.clear(notificationId);
          }, 7000);
        }
        playNotificationSound();
      }
      
    }
    const savedtimestampAfter = await getTimestampGroupAnnouncement();
    chrome.runtime.sendMessage({
      action: "SET_GROUP_ANNOUNCEMENTS",
      payload: savedtimestampAfter,
    });
  } catch (error) {
  } finally {
  }
};
const checkNewMessages = async () => {
  try {
    let mutedGroups = await getUserSettings({key: 'mutedGroups'}) || []
    if(!isArray(mutedGroups)) mutedGroups = []
    mutedGroups.push('0')
    let myName = "";
    const userData = await getUserInfo();
    if (userData?.name) {
      myName = userData.name;
    }

    let newAnnouncements = [];
    const activeData = (await getStoredData("active-groups-directs")) || {
      groups: [],
      directs: [],
    };
    const groups = activeData?.groups;
    if (!groups || groups?.length === 0) return;
    const savedtimestamp = await getTimestampGroupAnnouncement();

    await Promise.all(
      groups.map(async (group) => {
        try {
          const identifier = `grp-${group.groupId}-anc-`;
          const endpoint = await getArbitraryEndpoint();
          const url = await createEndpoint(
            `${endpoint}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=1&includemetadata=false&offset=0&reverse=true&prefix=true`
          );
          const response = await requestQueueAnnouncements.enqueue(() => {
            return fetch(url, {
              method: "GET",
              headers: {
                "Content-Type": "application/json",
              },
            });
          });
          const responseData = await response.json();

          const latestMessage = responseData.filter(
            (pub) => pub?.name !== myName
          )[0];
          if (!latestMessage) {
            return; // continue to the next group
          }

          if (
            checkDifference(latestMessage.created) &&
            (!savedtimestamp[group.groupId] ||
              latestMessage.created >
                savedtimestamp?.[group.groupId]?.notification)
          ) {
            newAnnouncements.push(group);
            await addTimestampGroupAnnouncement({
              groupId: group.groupId,
              timestamp: Date.now(),
            });
            // save new timestamp
          }
        } catch (error) {
          console.error(error); // Handle error if needed
        }
      })
    );
    let isDisableNotifications = await getUserSettings({key: 'disable-push-notifications'}) || false

    if (newAnnouncements.length > 0 && !mutedGroups.includes(newAnnouncements[0]?.groupId) && !isDisableNotifications) {
      const notificationId =
        "chat_notification_" +
        Date.now() +
        "_type=group-announcement" +
        `_from=${newAnnouncements[0]?.groupId}`;

      chrome.notifications.create(notificationId, {
        type: "basic",
        iconUrl: "qort.png", // Add an appropriate icon for chat notifications
        title: `New group announcement!`,
        message: `You have received a new announcement from ${newAnnouncements[0]?.groupName}`,
        priority: 2, // Use the maximum priority to ensure it's noticeable
        // buttons: [
        //   { title: 'Go to group' }
        // ]
      });
      if (!isMobile) {
        setTimeout(() => {
          chrome.notifications.clear(notificationId);
        }, 7000);
      }
      playNotificationSound();
    }
    const savedtimestampAfter = await getTimestampGroupAnnouncement();
    chrome.runtime.sendMessage({
      action: "SET_GROUP_ANNOUNCEMENTS",
      payload: savedtimestampAfter,
    });
  } catch (error) {
  } finally {
  }
};

const listenForNewGroupAnnouncements = async () => {
  try {
    setTimeout(() => {
      checkNewMessages();
    }, 500);
    if (interval) {
      clearInterval(interval);
    }

    let isCalling = false;
    interval = setInterval(async () => {
      if (isCalling) return;
      isCalling = true;
      const res = await checkNewMessages();
      isCalling = false;
    }, 180000);
  } catch (error) {}
};
const listenForThreadUpdates = async () => {
  try {
    setTimeout(() => {
      checkThreads();
    }, 500);
    if (intervalThreads) {
      clearInterval(intervalThreads);
    }

    let isCalling = false;
    intervalThreads = setInterval(async () => {
      if (isCalling) return;
      isCalling = true;
      const res = await checkThreads();
      isCalling = false;
    }, 60000);
  } catch (error) {}
};

const forceCloseWebSocket = () => {
  if (socket) {
    clearTimeout(timeoutId);
    clearTimeout(groupSocketTimeout);
    clearTimeout(socketTimeout);
    timeoutId = null;
    groupSocketTimeout = null;
    socket.close(1000, "forced");
    socket = null;
  }
};

async function getNameInfo() {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const validApi = await getBaseApi();
  const response = await fetch(validApi + "/names/address/" + address);
  const nameData = await response.json();
  if (nameData?.length > 0) {
    return nameData[0].name;
  } else {
    return "";
  }
}
export async function getNameInfoForOthers(address) {
  const validApi = await getBaseApi();
  const response = await fetch(validApi + "/names/address/" + address);
  const nameData = await response.json();
  if (nameData?.length > 0) {
    return nameData[0].name;
  } else {
    return "";
  }
}
export async function getAddressInfo(address) {
  const validApi = await getBaseApi();
  const response = await fetch(validApi + "/addresses/" + address);
  const data = await response.json();

  if (!response?.ok && data?.error !== 124)
    throw new Error("Cannot fetch address info");
  if (data?.error === 124) {
    return {
      address,
    };
  }
  return data;
}
async function decryptData(encryptedData: ArrayBuffer, key: CryptoKey, iv: Uint8Array): Promise<string> {
  const decryptedData = await crypto.subtle.decrypt(
    {
      name: "AES-GCM",
      iv: iv
    },
    key,
    encryptedData
  );

  const decoder = new TextDecoder();
  return decoder.decode(decryptedData);
}

function base64ToJson(base64) {
  const binary = atob(base64);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return JSON.parse(new TextDecoder().decode(bytes));
}

export async function getKeyPair() {
  const res = await chrome.storage.local.get(["keyPair"]);
  if (res?.keyPair) {
    return res.keyPair;
  } else {
    throw new Error("Wallet not authenticated");
  }
}

export async function getSaveWallet() {
  const res = await chrome.storage.local.get(["walletInfo"]);
  if (res?.walletInfo) {
    return res.walletInfo;
  } else {
    throw new Error("No wallet saved");
  }
}

export async function getWallets() {
  const res = await chrome.storage.local.get(["wallets"]);
  if (res['wallets']) {
    return res['wallets'];
  } else {
    throw new Error("No wallet saved");
  }
}

export async function storeWallets(wallets) {
  chrome.storage.local.set({ wallets: wallets }, () => {
    if (chrome.runtime.lastError) {
      reject(new Error(chrome.runtime.lastError.message));
    } else {
      resolve(true);
    }
  });
}

async function clearAllNotifications() {
  const notifications = await chrome.notifications.getAll();
  for (const notificationId of Object.keys(notifications)) {
    await chrome.notifications.clear(notificationId);
  }
}

async function getUserInfo() {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const addressInfo = await getAddressInfo(address);
  const name = await getNameInfo();
  return {
    name,
    publicKey: wallet.publicKey,
    ...addressInfo,
  };
}

async function connection(hostname) {
  const isConnected = chrome.storage.local.get([hostname]);
  return isConnected;
}

async function getTradeInfo(qortalAtAddress) {
  const response = await fetch(
    buyTradeNodeBaseUrl + "/crosschain/trade/" + qortalAtAddress
  );
  if (!response?.ok) throw new Error("Cannot crosschain trade information");
  const data = await response.json();
  return data;
}
async function getTradesInfo(qortalAtAddresses) {
  // Use Promise.all to fetch data for all addresses concurrently
  const trades = await Promise.all(
    qortalAtAddresses.map((address) => getTradeInfo(address))
  );
  return trades; // Return the array of trade info objects
}

export async function getBalanceInfo() {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const validApi = await getBaseApi();
  const response = await fetch(validApi + "/addresses/balance/" + address);

  if (!response?.ok) throw new Error("0 QORT in your balance");
  const data = await response.json();
  return data;
}
async function getLTCBalance() {
  const wallet = await getSaveWallet();
  let _url = `${buyTradeNodeBaseUrl}/crosschain/ltc/walletbalance`;
  const keyPair = await getKeyPair();
  const parsedKeyPair = JSON.parse(keyPair);
  let _body = parsedKeyPair.ltcPublicKey;
  const response = await fetch(_url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: _body,
  });
  if (response?.ok) {
    const data = await response.text();
    const dataLTCBalance = (Number(data) / 1e8).toFixed(8);
    return +dataLTCBalance;
  } else throw new Error("Onable to get LTC balance");
}

const processTransactionVersion2Chat = async (body: any, customApi) => {
  // const validApi = await findUsableApi();
  const url = await createEndpoint(
    "/transactions/process?apiVersion=2",
    customApi
  );
  return fetch(url, {
    method: "POST",
    headers: {},
    body: Base58.encode(body),
  }).then(async (response) => {
    try {
      const json = await response.clone().json();
      return json;
    } catch (e) {
      return await response.text();
    }
  });
};

export const processTransactionVersion2 = async (body: any) => {
  const url = await createEndpoint(`/transactions/process?apiVersion=2`);

  try {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json", // Ensure the body is correctly parsed
      },
      body, // Convert body to JSON string
    });

    // if (!response.ok) {
    //   // If the response is not successful (status code is not 2xx)
    //   throw new Error(`HTTP error! Status: ${response.status}`);
    // }

    try {
      const json = await response.clone().json();
      return json;
    } catch (jsonError) {
      try {
        const text = await response.text();
        return text;
      } catch (textError) {
        throw new Error(`Failed to parse response as both JSON and text.`);
      }
    }
  } catch (error) {
    console.error("Error processing transaction:", error);
    throw error; // Re-throw the error after logging it
  }
};

const transaction = async (
  { type, params, apiVersion, keyPair }: any,
  validApi
) => {
  const tx = createTransaction(type, keyPair, params);
  let res;

  if (apiVersion && apiVersion === 2) {
    const signedBytes = Base58.encode(tx.signedBytes);
    res = await processTransactionVersion2(signedBytes, validApi);
  }
  let success = true;
  if (res?.error) {
    success = false;
  }

  return {
    success,
    data: res,
  };
};
const makeTransactionRequest = async (
  receiver,
  lastRef,
  amount,
  fee,
  keyPair,
  validApi
) => {
  const myTxnrequest = await transaction(
    {
      nonce: 0,
      type: 2,
      params: {
        recipient: receiver,
        // recipientName: recipientName,
        amount: amount,
        lastReference: lastRef,
        fee: fee,
      },
      apiVersion: 2,
      keyPair,
    },
    validApi
  );
  return myTxnrequest;
};

export const getLastRef = async () => {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const validApi = await getBaseApi();
  const response = await fetch(
    validApi + "/addresses/lastreference/" + address
  );
  if (!response?.ok) throw new Error("0 QORT in your balance");
  const data = await response.text();
  return data;
};
export const sendQortFee = async (): Promise<number> => {
  const validApi = await getBaseApi();
  const response = await fetch(
    validApi + "/transactions/unitfee?txType=PAYMENT"
  );

  if (!response.ok) {
    throw new Error("Error when fetching join fee");
  }

  const data = await response.json();
  const qortFee = (Number(data) / 1e8).toFixed(8);
  return qortFee;
};

export async function getNameOrAddress(receiver) {
  try {
    const isAddress = validateAddress(receiver);
    if (isAddress) {
      return receiver;
    }
    const validApi = await getBaseApi();

    const response = await fetch(validApi + "/names/" + receiver);
    const data = await response.json();
    if (data?.owner) return data.owner;
    if (data?.error) {
      throw new Error("Name does not exist");
    }
    if (!response?.ok) throw new Error("Cannot fetch name");
    return { error: "cannot validate address or name" };
  } catch (error) {
    throw new Error(error?.message || "cannot validate address or name");
  }
}

export async function getPublicKey(receiver) {
  try {
    const validApi = await getBaseApi();

    const response = await fetch(validApi + "/addresses/publickey/" + receiver);
    if (!response?.ok) throw new Error("Cannot fetch recipient's public key");

    const data = await response.text();
    if (!data?.error && data !== "false") return data;
    if (data?.error) {
      throw new Error("Cannot fetch recipient's public key");
    }
    throw new Error("Cannot fetch recipient's public key");
  } catch (error) {
    throw new Error(error?.message || "cannot validate address or name");
  }
}

const MAX_STORAGE_SIZE = 3 * 1024 * 1024; // 3MB in bytes

async function getDataPublishes(groupId, type) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;

  return new Promise((resolve) => {
    chrome.storage.local.get([`${address}-publishData`], (result) => {
      if (chrome.runtime.lastError) {
        console.error("Error retrieving data:", chrome.runtime.lastError);
        resolve(null); // Return null in case of an error
        return;
      }

      let storedData = result[`${address}-publishData`] || {}; // Get the stored data or initialize an empty object
      let groupData = storedData[groupId] || {}; // Get data by groupId
      let typeData = groupData[type] || {}; // Get data by type

      resolve(typeData); // Resolve with the data inside the specific type
    });
  });
}

async function sendChatForBuyOrder({ qortAddress, recipientPublicKey, message, isSingle }) {
  let _reference = new Uint8Array(64);
  self.crypto.getRandomValues(_reference);

  let sendTimestamp = Date.now();

  let reference = Base58.encode(_reference);
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };
  const balance = await getBalanceInfo();
  const hasEnoughBalance = +balance < 4 ? false : true;
  const difficulty = 8;
  const jsonData = {
    addresses: message.addresses,
    foreignKey: message.foreignKey,
    receivingAddress: message.receivingAddress,
  };
  const finalJson = {
    callRequest: jsonData,
    extra: {
      type: isSingle ? "single" : "multiple"
    },
  };
  const messageStringified = JSON.stringify(finalJson);

  const tx = await createTransaction(18, keyPair, {
    timestamp: sendTimestamp,
    recipient: qortAddress,
    recipientPublicKey: recipientPublicKey,
    hasChatReference: 0,
    message: messageStringified,
    lastReference: reference,
    proofOfWorkNonce: 0,
    isEncrypted: 1,
    isText: 1,
  });
  if (!hasEnoughBalance) {
    throw new Error('You must have at least 4 QORT to trade using the gateway.')
  }
  const path = `${import.meta.env.BASE_URL}memory-pow.wasm.full`;

  const { nonce, chatBytesArray } = await computePow({
    chatBytes: tx.chatBytes,
    path,
    difficulty,
  });
  let _response = await signChatFunc(
    chatBytesArray,
    nonce,
    "https://appnode.qortal.org",
    keyPair
  );
  if (_response?.error) {
    throw new Error(_response?.message);
  }
  return _response;
}

async function addDataPublishes(newData, groupId, type) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const nameIdentifier = `${newData.name}-${newData.identifier}`;

  // Prevent adding data larger than 50KB
  if (newData?.size > 50000) return false;

  return new Promise((res) => {
    chrome.storage.local.get([`${address}-publishData`], (result) => {
      let storedData = result[`${address}-publishData`] || {}; // Get existing data or initialize
      let groupData = storedData[groupId] || {}; // Get or initialize group by groupId
      let typeData = groupData[type] || {}; // Get or initialize the type within the group

      let totalSize = 0;

      // Calculate the current size of all stored data
      Object.values(storedData).forEach((group) => {
        Object.values(group).forEach((type) => {
          Object.values(type).forEach((data) => {
            totalSize += data.size; // Accumulate the sizes of actual data
          });
        });
      });

      // Check if adding the new data exceeds 3MB
      if (totalSize + newData.size > MAX_STORAGE_SIZE) {
        // Sort and remove older data within the group and type
        let dataEntries = Object.entries(typeData);
        dataEntries.sort((a, b) => a[1].timestampSaved - b[1].timestampSaved);

        // Remove old data until there's enough space
        while (
          totalSize + newData.size > MAX_STORAGE_SIZE &&
          dataEntries.length > 0
        ) {
          const removedEntry = dataEntries.shift();
          totalSize -= removedEntry[1].size;
          delete typeData[removedEntry[0]]; // Remove from the typeData
        }
      }

      // Add or update the new data within the group and type
      if (totalSize + newData.size <= MAX_STORAGE_SIZE) {
        typeData[`${nameIdentifier}`] = newData; // Add new data under name-identifier
        groupData[type] = typeData; // Update type data within the group
        storedData[groupId] = groupData; // Update group data within the stored data

        // Save the updated structure back to chrome.storage.local
        chrome.storage.local.set(
          { [`${address}-publishData`]: storedData },
          () => {
            res(true); // Data successfully added
          }
        );
      } else {
        console.error("Failed to add data, still exceeds storage limit.");
        res(false); // Failure due to storage limit
      }
    });
  });
}

// Fetch user settings based on the key
async function getUserSettings({ key }) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;

  return new Promise((resolve) => {
    chrome.storage.local.get([`${address}-userSettings`], (result) => {
      if (chrome.runtime.lastError) {
        console.error("Error retrieving data:", chrome.runtime.lastError);
        resolve(null); // Return null in case of an error
        return;
      }

      const storedData = result[`${address}-userSettings`] || {}; // Get the stored data or initialize an empty object
      const value = storedData[key] || null; // Get data by key

      resolve(value); // Resolve with the data for the specific key
    });
  });
}

// Add or update user settings
async function addUserSettings({ keyValue }) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const { key, value } = keyValue;

  // No need to check size here, unless value is a large object. For simple settings, size checks aren't necessary.

  return new Promise((res) => {
    chrome.storage.local.get([`${address}-userSettings`], (result) => {
      let storedData = result[`${address}-userSettings`] || {}; // Get existing data or initialize

      storedData[key] = value; // Update the key-value pair within the stored data

      // Save the updated structure back to chrome.storage.local
      chrome.storage.local.set(
        { [`${address}-userSettings`]: storedData },
        () => {
          res(true); // Data successfully added
        }
      );
    });
  });
}

async function decryptWallet({ password, wallet, walletVersion }) {
  try {
    const response = await decryptStoredWallet(password, wallet);
    const wallet2 = new PhraseWallet(response, walletVersion);
    const keyPair = wallet2._addresses[0].keyPair;
    const ltcPrivateKey =
      wallet2._addresses[0].ltcWallet.derivedMasterPrivateKey;
    const ltcPublicKey = wallet2._addresses[0].ltcWallet.derivedMasterPublicKey;
    const ltcAddress = wallet2._addresses[0].ltcWallet.address;
    const toSave = {
      privateKey: Base58.encode(keyPair.privateKey),
      publicKey: Base58.encode(keyPair.publicKey),
      ltcPrivateKey: ltcPrivateKey,
      ltcPublicKey: ltcPublicKey,
      arrrSeed58: wallet2._addresses[0].arrrWallet.seed58,
      btcAddress: wallet2._addresses[0].btcWallet.address,
      btcPublicKey: wallet2._addresses[0].btcWallet.derivedMasterPublicKey,
      btcPrivateKey: wallet2._addresses[0].btcWallet.derivedMasterPrivateKey,

      ltcAddress: wallet2._addresses[0].ltcWallet.address,

      dogeAddress: wallet2._addresses[0].dogeWallet.address,
      dogePublicKey: wallet2._addresses[0].dogeWallet.derivedMasterPublicKey,
      dogePrivateKey: wallet2._addresses[0].dogeWallet.derivedMasterPrivateKey,

      dgbAddress: wallet2._addresses[0].dgbWallet.address,
      dgbPublicKey: wallet2._addresses[0].dgbWallet.derivedMasterPublicKey,
      dgbPrivateKey: wallet2._addresses[0].dgbWallet.derivedMasterPrivateKey,

      rvnAddress: wallet2._addresses[0].rvnWallet.address,
      rvnPublicKey: wallet2._addresses[0].rvnWallet.derivedMasterPublicKey,
      rvnPrivateKey: wallet2._addresses[0].rvnWallet.derivedMasterPrivateKey
    };
    const dataString = JSON.stringify(toSave);
    await new Promise((resolve, reject) => {
      chrome.storage.local.set({ keyPair: dataString }, () => {
        if (chrome.runtime.lastError) {
          reject(new Error(chrome.runtime.lastError.message));
        } else {
          resolve(true);
        }
      });
    });
    const newWallet = {
      ...wallet,
      publicKey: Base58.encode(keyPair.publicKey),
      ltcAddress: ltcAddress,
    };
    await new Promise((resolve, reject) => {
      chrome.storage.local.set({ walletInfo: newWallet }, () => {
        if (chrome.runtime.lastError) {
          reject(new Error(chrome.runtime.lastError.message));
        } else {
          resolve(true);
        }
      });
    });

    return true;
  } catch (error) {
    throw new Error(error.message);
  }
}

export async function signChatFunc(chatBytesArray, chatNonce, customApi, keyPair) {
  let response;
  try {
    const signedChatBytes = signChat(chatBytesArray, chatNonce, keyPair);

    const res = await processTransactionVersion2Chat(
      signedChatBytes,
      customApi
    );
    response = res;
  } catch (e) {
    console.error(e);
    console.error(e.message);
    response = false;
  }
  return response;
}
function sbrk(size, heap) {
  let brk = 512 * 1024; // stack top
  let old = brk;
  brk += size;
  if (brk > heap.length) throw new Error("heap exhausted");
  return old;
}

export const computePow = async ({ chatBytes, path, difficulty }) => {
  let response = null;
  await new Promise((resolve, reject) => {
    const _chatBytesArray = Object.keys(chatBytes).map(function (key) {
      return chatBytes[key];
    });
    const chatBytesArray = new Uint8Array(_chatBytesArray);
    const chatBytesHash = new Sha256().process(chatBytesArray).finish().result;
    const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 });
    const heap = new Uint8Array(memory.buffer);

    const hashPtr = sbrk(32, heap);
    const hashAry = new Uint8Array(memory.buffer, hashPtr, 32);
    hashAry.set(chatBytesHash);
    const workBufferLength = 8 * 1024 * 1024;
    const workBufferPtr = sbrk(workBufferLength, heap);
    const importObject = {
      env: {
        memory: memory,
      },
    };
    function loadWebAssembly(filename, imports) {
      // Fetch the file and compile it
      return fetch(filename)
        .then((response) => response.arrayBuffer())
        .then((buffer) => WebAssembly.compile(buffer))
        .then((module) => {
          // Create the instance.
          return new WebAssembly.Instance(module, importObject);
        });
    }
    loadWebAssembly(path).then((wasmModule) => {
      response = {
        nonce: wasmModule.exports.compute2(
          hashPtr,
          workBufferPtr,
          workBufferLength,
          difficulty
        ),
        chatBytesArray,
      };
      resolve();
    });
  });
  return response;
};

const getStoredData = async (key) => {
  return new Promise((resolve, reject) => {
    chrome.storage.local.get(key, (result) => {
      if (chrome.runtime.lastError) {
        return reject(chrome.runtime.lastError);
      }
      resolve(result[key]);
    });
  });
};

async function handleActiveGroupDataFromSocket({ groups, directs }) {
  try {
    chrome.runtime.sendMessage({
      action: "SET_GROUPS",
      payload: groups,
    });
    chrome.runtime.sendMessage({
      action: "SET_DIRECTS",
      payload: directs,
    });
    groups = groups;
    directs = directs;
    const activeData = {
      groups: groups || [], // Your groups data here
      directs: directs || [], // Your directs data here
    };

    // Save the active data to localStorage
    chrome.storage.local.set({ "active-groups-directs": activeData });
    try {
      handleNotification(groups);
      handleNotificationDirect(directs);
    } catch (error) {}
  } catch (error) {}
}

async function sendChat({ qortAddress, recipientPublicKey, message }) {
  let _reference = new Uint8Array(64);
  self.crypto.getRandomValues(_reference);

  let sendTimestamp = Date.now();

  let reference = Base58.encode(_reference);
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };
  const balance = await getBalanceInfo();
  const hasEnoughBalance = +balance < 4 ? false : true;
  const difficulty = 8;
  const jsonData = {
    addresses: message.addresses,
    foreignKey: message.foreignKey,
    receivingAddress: message.receivingAddress,
  };
  const finalJson = {
    callRequest: jsonData,
    extra: "whatever additional data goes here",
  };
  const messageStringified = JSON.stringify(finalJson);

  const tx = await createTransaction(18, keyPair, {
    timestamp: sendTimestamp,
    recipient: qortAddress,
    recipientPublicKey: recipientPublicKey,
    hasChatReference: 0,
    message: messageStringified,
    lastReference: reference,
    proofOfWorkNonce: 0,
    isEncrypted: 1,
    isText: 1,
  });
  if (!hasEnoughBalance) {
    const _encryptedMessage = tx._encryptedMessage;
    const encryptedMessageToBase58 = Base58.encode(_encryptedMessage);
    return {
      encryptedMessageToBase58,
      signature: "id-" + Date.now() + "-" + Math.floor(Math.random() * 1000),
      reference,
    };
  }
  const path = chrome.runtime.getURL("memory-pow.wasm.full");

  const { nonce, chatBytesArray } = await computePow({
    chatBytes: tx.chatBytes,
    path,
    difficulty,
  });
  let _response = await signChatFunc(
    chatBytesArray,
    nonce,
    "https://appnode.qortal.org",
    keyPair
  );
  if (_response?.error) {
    throw new Error(_response?.message);
  }
  return _response;
}

export async function getTimestampLatestPayment() {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const key = `latest-payment-${address}`;
 
  return new Promise((resolve, reject) => {
    chrome.storage.local.get([key], (result) => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message || "Error retrieving data"));
      } else {
        const timestamp = result[key];
        resolve(timestamp || 0);
      }
    });
  });
}

export async function addTimestampLatestPayment(timestamp) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  

  return new Promise((resolve, reject) => {
    chrome.storage.local.set({ [`latest-payment-${address}`]: timestamp }, () => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message || "Error saving data"));
      } else {
        resolve(true);
      }
    });
  });
}


export async function addEnteredQmailTimestampFunc() {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const key = `qmail-entered-timestamp-${address}`;
  
  return new Promise((resolve, reject) => {
    chrome.storage.local.set({ [key]: Date.now() }, () => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message || "Error saving data"));
      } else {
        resolve(true);
      }
    });
  });
}

// Function to retrieve the timestamp from Chrome's local storage
export async function getEnteredQmailTimestampFunc() {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const key = `qmail-entered-timestamp-${address}`;
  
  return new Promise((resolve, reject) => {
    chrome.storage.local.get([key], (result) => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message || "Error retrieving data"));
      } else {
        const timestamp = result[key];
        resolve(timestamp || null);
      }
    });
  });
}

 async function addEnteredQmailTimestamp() {

    const response = await addEnteredQmailTimestampFunc();

    return response
}
 async function getEnteredQmailTimestamp() {
  
    const response = await getEnteredQmailTimestampFunc();

   return {timestamp: response}
  
}

async function sendChatGroup({
  groupId,
  typeMessage,
  chatReference,
  messageText,
}) {
  
  let _reference = new Uint8Array(64);
  self.crypto.getRandomValues(_reference);

  let reference = Base58.encode(_reference);
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };
  // const balance = await getBalanceInfo();
  // const hasEnoughBalance = +balance < 4 ? false : true;
  const difficulty = 8;

  const txBody = {
    timestamp: Date.now(),
    groupID: Number(groupId),
    hasReceipient: 0,
    hasChatReference: chatReference ? 1 : 0,
    message: messageText,
    lastReference: reference,
    proofOfWorkNonce: 0,
    isEncrypted: 0, // Set default to not encrypted for groups
    isText: 1,
  }

  if(chatReference){
    txBody['chatReference'] = chatReference
  }

  const tx = await createTransaction(181, keyPair, txBody);

  // if (!hasEnoughBalance) {
  //   throw new Error("Must have at least 4 QORT to send a chat message");
  // }
  const path = chrome.runtime.getURL("memory-pow.wasm.full");

  const { nonce, chatBytesArray } = await computePow({
    chatBytes: tx.chatBytes,
    path,
    difficulty,
  });
  let _response = await signChatFunc(chatBytesArray, nonce, null, keyPair);
  if (_response?.error) {
    throw new Error(_response?.message);
  }
  return _response;
}

async function sendChatDirect({
  address,
  directTo,
  typeMessage,
  chatReference,
  messageText,
  publicKeyOfRecipient,
  otherData
}) {
  let recipientPublicKey;
  let recipientAddress = address;
  if (publicKeyOfRecipient) {
    recipientPublicKey = publicKeyOfRecipient;
  } else {
    recipientAddress = await getNameOrAddress(directTo);
    recipientPublicKey = await getPublicKey(recipientAddress);
  }
  if (!recipientAddress) {
    recipientAddress = await getNameOrAddress(directTo);
  }

  if (!recipientPublicKey) throw new Error("Cannot retrieve publickey");

  let _reference = new Uint8Array(64);
  self.crypto.getRandomValues(_reference);

  let reference = Base58.encode(_reference);
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };
  // const balance = await getBalanceInfo();
  // const hasEnoughBalance = +balance < 4 ? false : true;

  const difficulty = 8;

  const finalJson = {
    message: messageText,
    version: 2,
    ...(otherData || {})
  };
  const messageStringified = JSON.stringify(finalJson);
  
  const txBody = {
    timestamp: Date.now(),
    recipient: recipientAddress,
    recipientPublicKey: recipientPublicKey,
    hasChatReference: chatReference ? 1 : 0,
    message: messageStringified,
    lastReference: reference,
    proofOfWorkNonce: 0,
    isEncrypted: 1,
    isText: 1,
  }
  if(chatReference){
    txBody['chatReference'] = chatReference
  }
  const tx = await createTransaction(18, keyPair, txBody);

  // if (!hasEnoughBalance) {
  //   throw new Error("Must have at least 4 QORT to send a chat message");
  // }
  const path = chrome.runtime.getURL("memory-pow.wasm.full");

  const { nonce, chatBytesArray } = await computePow({
    chatBytes: tx.chatBytes,
    path,
    difficulty,
  });

  let _response = await signChatFunc(chatBytesArray, nonce, null, keyPair);
  if (_response?.error) {
    throw new Error(_response?.message);
  }
  return _response;
}

async function decryptSingleFunc({
  messages,
  secretKeyObject,
  skipDecodeBase64,
}) {
  let holdMessages = [];

  for (const message of messages) {
    try {
      const res = await decryptSingle({
        data64: message.data,
        secretKeyObject,
        skipDecodeBase64,
      });

      const decryptToUnit8Array = base64ToUint8Array(res);
      const responseData = uint8ArrayToObject(decryptToUnit8Array);
      holdMessages.push({ ...message, decryptedData: responseData });
    } catch (error) {}
  }
  return holdMessages;
}
async function decryptSingleForPublishes({
  messages,
  secretKeyObject,
  skipDecodeBase64,
}) {
  let holdMessages = [];

  for (const message of messages) {
    try {
      const res = await decryptSingle({
        data64: message.data,
        secretKeyObject,
        skipDecodeBase64,
      });

      const decryptToUnit8Array = base64ToUint8Array(res);
      const responseData = uint8ArrayToObject(decryptToUnit8Array);
      holdMessages.push({ ...message, decryptedData: responseData });
    } catch (error) {}
  }
  return holdMessages;
}

async function decryptDirectFunc({ messages, involvingAddress }) {
  const senderPublicKey = await getPublicKey(involvingAddress);
  let holdMessages = [];

  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };
  for (const message of messages) {
    try {
      const decodedMessage = decryptChatMessage(
        message.data,
        keyPair.privateKey,
        senderPublicKey,
        message.reference
      );
      const parsedMessage = JSON.parse(decodedMessage);
      holdMessages.push({ ...message, ...parsedMessage });
    } catch (error) {}
  }
  return holdMessages;
}

async function createBuyOrderTx({ crosschainAtInfo, useLocal }) {
  try {
    if(useLocal){
      const wallet = await getSaveWallet();

      const address = wallet.address0;

      const resKeyPair = await getKeyPair();
      const parsedData = JSON.parse(resKeyPair);
      const message = {
        addresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
        foreignKey: parsedData.ltcPrivateKey,
        receivingAddress: address,
      };
      let responseVar 
      const txn = new TradeBotRespondMultipleRequest().createTransaction(message)
      const apiKey = await getApiKeyFromStorage();
      const responseFetch = await fetch(`${apiKey?.url}/crosschain/tradebot/respondmultiple?apiKey=${apiKey?.apikey}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(txn),
  });

  const res = await responseFetch.json();

      if(res === false){
        responseVar = { response: "Unable to execute buy order", success: false };
      } else {
        responseVar = { response: res, success: true };
      }
      const { response, success } = responseVar
      let responseMessage;
      if (success) {
          responseMessage = {
              callResponse: response,
              extra: {
                  message: 'Transaction processed successfully!',
                  atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
                  
              }
          };
      } else {
          responseMessage = {
              callResponse: 'ERROR',
              extra: {
                  message: response,
                  atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
               
              }
          };
      }

      setTimeout(() => {
        chrome.tabs.query({}, function (tabs) {
          tabs.forEach((tab) => {
            chrome.tabs.sendMessage(tab.id, {
              type: "RESPONSE_FOR_TRADES",
              message: responseMessage,
            });
          });
        });
      }, 5000);

      return
    }
    const wallet = await getSaveWallet();
    const address = wallet.address0;

    const resKeyPair = await getKeyPair();
    const parsedData = JSON.parse(resKeyPair);
    const message = {
      addresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
      foreignKey: parsedData.ltcPrivateKey,
      receivingAddress: address,
    };
    const res = await sendChat({
      qortAddress: proxyAccountAddress,
      recipientPublicKey: proxyAccountPublicKey,
      message,
    });
    if (res?.signature) {
      listenForChatMessageForBuyOrder({
        nodeBaseUrl: buyTradeNodeBaseUrl,
        senderAddress: proxyAccountAddress,
        senderPublicKey: proxyAccountPublicKey,
        signature: res?.signature,
      });
      if (res?.encryptedMessageToBase58) {
        return {
          atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
          encryptedMessageToBase58: res?.encryptedMessageToBase58,
          node: buyTradeNodeBaseUrl,
          qortAddress: address,
          chatSignature: res?.signature,
          senderPublicKey: parsedData.publicKey,
          sender: address,
          reference: res?.reference,
        };
      }
      return {
        atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
        chatSignature: res?.signature,
        node: buyTradeNodeBaseUrl,
        qortAddress: address,
      };
    } else {
      throw new Error("Unable to send buy order message");
    }
  } catch (error) {
    throw new Error(error.message);
  }
}


export async function createBuyOrderTxQortalRequest({ crosschainAtInfo, isGateway, foreignBlockchain }) {
  try {

    if (!isGateway) {
      const wallet = await getSaveWallet();

      const address = wallet.address0;
      let message
      if(foreignBlockchain === 'PIRATECHAIN'){
         message = {
          atAddress: crosschainAtInfo[0].qortalAtAddress,
          foreignKey: await getForeignKey(foreignBlockchain),
          receivingAddress: address,
        };
      } else {
         message = {
          addresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
          foreignKey: await getForeignKey(foreignBlockchain),
          receivingAddress: address,
        };
      }
     
      let responseVar;
      let txn
      let url
      if(foreignBlockchain === 'PIRATECHAIN'){
         txn = new TradeBotRespondRequest().createTransaction(
          message
        );
       
     
          url =  await createEndpoint('/crosschain/tradebot/respond')
      } else {
         txn = new TradeBotRespondMultipleRequest().createTransaction(
          message
        );
       
     
          url =  await createEndpoint('/crosschain/tradebot/respondmultiple')
      }
    
      const responseFetch = await fetch(
        url,
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify(txn),
        }
      );

      const res = await responseFetch.json();
      if(res?.error && res?.message){
        throw new Error(res?.message)
      }
      if(!responseFetch?.ok) throw new Error('Failed to submit buy order')

      if (res === false) {
        responseVar = {
          response: "Unable to execute buy order",
          success: false,
        };
      } else {
        responseVar = { response: res, success: true };
      }
      const { response, success } = responseVar;
      let responseMessage;
      if (success) {
        responseMessage = {
          callResponse: response,
          extra: {
            message: "Transaction processed successfully!",
            atAddresses: foreignBlockchain === 'PIRATECHAIN' ? [crosschainAtInfo[0].qortalAtAddress]  : crosschainAtInfo.map((order)=> order.qortalAtAddress),
            senderAddress: address,
            node: url
          },
        };
      } else {
        responseMessage = {
          callResponse: "ERROR",
          extra: {
            message: response,
            atAddresses: foreignBlockchain === 'PIRATECHAIN' ? [crosschainAtInfo[0].qortalAtAddress]  : crosschainAtInfo.map((order)=> order.qortalAtAddress),
            senderAddress: address,
            node: url
          },
        };
      }

      return responseMessage
    }
    const wallet = await getSaveWallet();
    const address = wallet.address0;

 
    const message = {
      addresses: foreignBlockchain === 'PIRATECHAIN' ? [crosschainAtInfo[0].qortalAtAddress]  : crosschainAtInfo.map((order)=> order.qortalAtAddress),
      foreignKey: await getForeignKey(foreignBlockchain),
      receivingAddress: address,
    };
    const res = await sendChatForBuyOrder({
      qortAddress: proxyAccountAddress,
      recipientPublicKey: proxyAccountPublicKey,
      message,
      atAddresses: foreignBlockchain === 'PIRATECHAIN' ? [crosschainAtInfo[0].qortalAtAddress]  : crosschainAtInfo.map((order)=> order.qortalAtAddress),
      isSingle: foreignBlockchain === 'PIRATECHAIN'
    });


    
    if (res?.signature) {
    
        const message = await listenForChatMessageForBuyOrderQortalRequest({
          nodeBaseUrl: buyTradeNodeBaseUrl,
          senderAddress: proxyAccountAddress,
          senderPublicKey: proxyAccountPublicKey,
          signature: res?.signature,
        });

      const responseMessage = {
          callResponse: message.callResponse,
            extra: {
              message: message?.extra?.message,
              senderAddress: address,
              atAddresses: foreignBlockchain === 'PIRATECHAIN' ? [crosschainAtInfo[0].qortalAtAddress]  : crosschainAtInfo.map((order)=> order.qortalAtAddress),
            }
          }
    
      return responseMessage
    } else {
      throw new Error("Unable to send buy order message");
    }
  } catch (error) {
    throw new Error(error.message);
  }
}

async function sendChatNotification(
  res,
  groupId,
  secretKeyObject,
  numberOfMembers
) {
  try {
    const data = await objectToBase64({
      type: "notification",
      subType: "new-group-encryption",
      data: {
        timestamp: res.timestamp,
        name: res.name,
        message: `${res.name} has updated the encryption key`,
        numberOfMembers,
      },
    });

    encryptSingle({
      data64: data,
      secretKeyObject: secretKeyObject,
    })
      .then((res2) => {
        pauseAllQueues();
        sendChatGroup({
          groupId,
          typeMessage: undefined,
          chatReference: undefined,
          messageText: res2,
        })
          .then(() => {})
          .catch((error) => {
            console.error("1", error.message);
          })
          .finally(() => {
            resumeAllQueues();
          });
      })
      .catch((error) => {
        console.error("2", error.message);
      });
  } catch (error) {}
}

export const getFee = async (txType) => {
  const timestamp = Date.now();
  const data = await reusableGet(
    `/transactions/unitfee?txType=${txType}&timestamp=${timestamp}`
  );
  const arbitraryFee = (Number(data) / 1e8).toFixed(8);

  return {
    timestamp,
    fee: arbitraryFee,
  };
};

export async function leaveGroup({ groupId }) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const lastReference = await getLastRef();
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };
  const feeres = await getFee("LEAVE_GROUP");

  const tx = await createTransaction(32, keyPair, {
    fee: feeres.fee,
    registrantAddress: address,
    rGroupId: groupId,
    lastReference: lastReference,
  });

  const signedBytes = Base58.encode(tx.signedBytes);

  const res = await processTransactionVersion2(signedBytes);
  if (!res?.signature)
    throw new Error(res?.message || "Transaction was not able to be processed");
  return res;
}

export async function joinGroup({ groupId }) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const lastReference = await getLastRef();
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };
  const feeres = await getFee("JOIN_GROUP");

  const tx = await createTransaction(31, keyPair, {
    fee: feeres.fee,
    registrantAddress: address,
    rGroupId: groupId,
    lastReference: lastReference,
  });

  const signedBytes = Base58.encode(tx.signedBytes);

  const res = await processTransactionVersion2(signedBytes);
  if (!res?.signature)
    throw new Error(res?.message || "Transaction was not able to be processed");
  return res;
}

export async function cancelInvitationToGroup({ groupId, qortalAddress }) {
  const lastReference = await getLastRef();
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };
  const feeres = await getFee("CANCEL_GROUP_INVITE");

  const tx = await createTransaction(30, keyPair, {
    fee: feeres.fee,
    recipient: qortalAddress,
    rGroupId: groupId,
    lastReference: lastReference,
  });

  const signedBytes = Base58.encode(tx.signedBytes);

  const res = await processTransactionVersion2(signedBytes);
  if (!res?.signature)
    throw new Error(res?.message || "Transaction was not able to be processed");
  return res;
}

export async function cancelBan({ groupId, qortalAddress }) {
  const lastReference = await getLastRef();
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };
  const feeres = await getFee("CANCEL_GROUP_BAN");

  const tx = await createTransaction(27, keyPair, {
    fee: feeres.fee,
    recipient: qortalAddress,
    rGroupId: groupId,
    lastReference: lastReference,
  });

  const signedBytes = Base58.encode(tx.signedBytes);

  const res = await processTransactionVersion2(signedBytes);
  if (!res?.signature)
    throw new Error(res?.message || "Transaction was not able to be processed");
  return res;
}
export async function registerName({ name, description = "" }) {
  const lastReference = await getLastRef();
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };
  const feeres = await getFee("REGISTER_NAME");

  const tx = await createTransaction(3, keyPair, {
    fee: feeres.fee,
    name,
    value: description || "",
    lastReference: lastReference,
  });

  const signedBytes = Base58.encode(tx.signedBytes);

  const res = await processTransactionVersion2(signedBytes);
  if (!res?.signature)
    throw new Error(res?.message || "Transaction was not able to be processed");
  return res;
}

export async function updateName({ newName, oldName, description }) {
  const lastReference = await getLastRef();
  const resKeyPair = await getKeyPair();
  const parsedData = resKeyPair;
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };
  const feeres = await getFee("UPDATE_NAME");

  const tx = await createTransaction(4, keyPair, {
    fee: feeres.fee,
    name: oldName,
    newName,
    newData: description || "",
    lastReference: lastReference,
  });

  const signedBytes = Base58.encode(tx.signedBytes);

  const res = await processTransactionVersion2(signedBytes);
  if (!res?.signature)
    throw new Error(res?.message || "Transaction was not able to be processed");
  return res;
}

export async function makeAdmin({ groupId, qortalAddress }) {
  const lastReference = await getLastRef();
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };
  const feeres = await getFee("ADD_GROUP_ADMIN");

  const tx = await createTransaction(24, keyPair, {
    fee: feeres.fee,
    recipient: qortalAddress,
    rGroupId: groupId,
    lastReference: lastReference,
  });

  const signedBytes = Base58.encode(tx.signedBytes);

  const res = await processTransactionVersion2(signedBytes);
  if (!res?.signature)
    throw new Error(res?.message || "Transaction was not able to be processed");
  return res;
}

export async function removeAdmin({ groupId, qortalAddress }) {
  const lastReference = await getLastRef();
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };
  const feeres = await getFee("REMOVE_GROUP_ADMIN");

  const tx = await createTransaction(25, keyPair, {
    fee: feeres.fee,
    recipient: qortalAddress,
    rGroupId: groupId,
    lastReference: lastReference,
  });

  const signedBytes = Base58.encode(tx.signedBytes);

  const res = await processTransactionVersion2(signedBytes);
  if (!res?.signature)
    throw new Error(res?.message || "Transaction was not able to be processed");
  return res;
}

export async function banFromGroup({
  groupId,
  qortalAddress,
  rBanReason = "",
  rBanTime,
}) {
  const lastReference = await getLastRef();
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };
  const feeres = await getFee("GROUP_BAN");

  const tx = await createTransaction(26, keyPair, {
    fee: feeres.fee,
    recipient: qortalAddress,
    rGroupId: groupId,
    rBanReason: rBanReason,
    rBanTime,
    lastReference: lastReference,
  });

  const signedBytes = Base58.encode(tx.signedBytes);

  const res = await processTransactionVersion2(signedBytes);
  if (!res?.signature)
    throw new Error(res?.message || "Transaction was not able to be processed");
  return res;
}

export async function kickFromGroup({ groupId, qortalAddress, rBanReason = "" }) {
  const lastReference = await getLastRef();
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };
  const feeres = await getFee("GROUP_KICK");

  const tx = await createTransaction(28, keyPair, {
    fee: feeres.fee,
    recipient: qortalAddress,
    rGroupId: groupId,
    rBanReason: rBanReason,
    lastReference: lastReference,
  });

  const signedBytes = Base58.encode(tx.signedBytes);

  const res = await processTransactionVersion2(signedBytes);
  if (!res?.signature)
    throw new Error(res?.message || "Transaction was not able to be processed");
  return res;
}

export async function createGroup({
  groupName,
  groupDescription,
  groupType,
  groupApprovalThreshold,
  minBlock,
  maxBlock,
}) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  if (!address) throw new Error("Cannot find user");
  const lastReference = await getLastRef();
  const feeres = await getFee("CREATE_GROUP");
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };

  const tx = await createTransaction(22, keyPair, {
    fee: feeres.fee,
    registrantAddress: address,
    rGroupName: groupName,
    rGroupDesc: groupDescription,
    rGroupType: groupType,
    rGroupApprovalThreshold: groupApprovalThreshold,
    rGroupMinimumBlockDelay: minBlock,
    rGroupMaximumBlockDelay: maxBlock,
    lastReference: lastReference,
  });

  const signedBytes = Base58.encode(tx.signedBytes);

  const res = await processTransactionVersion2(signedBytes);
  if (!res?.signature) throw new Error(res?.message || "Transaction was not able to be processed");
  return res;
}
export async function inviteToGroup({ groupId, qortalAddress, inviteTime }) {
  const address = await getNameOrAddress(qortalAddress);
  if (!address) throw new Error("Cannot find user");
  const lastReference = await getLastRef();
  const feeres = await getFee("GROUP_INVITE");
  const resKeyPair = await getKeyPair();
  const parsedData = JSON.parse(resKeyPair);
  const uint8PrivateKey = Base58.decode(parsedData.privateKey);
  const uint8PublicKey = Base58.decode(parsedData.publicKey);
  const keyPair = {
    privateKey: uint8PrivateKey,
    publicKey: uint8PublicKey,
  };

  const tx = await createTransaction(29, keyPair, {
    fee: feeres.fee,
    recipient: address,
    rGroupId: groupId,
    rInviteTime: inviteTime,
    lastReference: lastReference,
  });

  const signedBytes = Base58.encode(tx.signedBytes);

  const res = await processTransactionVersion2(signedBytes);
  if (!res?.signature)
    throw new Error("Transaction was not able to be processed");
  return res;
}

export async function sendCoin({ password, amount, receiver }, skipConfirmPassword) {
  try {
    const confirmReceiver = await getNameOrAddress(receiver);
    if (confirmReceiver.error)
      throw new Error("Invalid receiver address or name");
    const wallet = await getSaveWallet();
    let keyPair = "";
    if (skipConfirmPassword) {
      const resKeyPair = await getKeyPair();
      const parsedData = JSON.parse(resKeyPair);
      const uint8PrivateKey = Base58.decode(parsedData.privateKey);
      const uint8PublicKey = Base58.decode(parsedData.publicKey);
      keyPair = {
        privateKey: uint8PrivateKey,
        publicKey: uint8PublicKey,
      };
    } else {
      const response = await decryptStoredWallet(password, wallet);
      const wallet2 = new PhraseWallet(response, wallet?.version || walletVersion);

      keyPair = wallet2._addresses[0].keyPair;
    }

    const lastRef = await getLastRef();
    const fee = await sendQortFee();
    const validApi = null;

    const res = await makeTransactionRequest(
      confirmReceiver,
      lastRef,
      amount,
      fee,
      keyPair,
      validApi
    );

    return { res, validApi };
  } catch (error) {
    throw new Error(error.message);
  }
}

function fetchMessages(apiCall) {
  let retryDelay = 2000; // Start with a 2-second delay
  const maxDuration = 360000 * 2; // Maximum duration set to 12 minutes
  const startTime = Date.now(); // Record the start time

  // Promise to handle polling logic
  return new Promise((resolve, reject) => {
    const attemptFetch = async () => {
      if (Date.now() - startTime > maxDuration) {
        return reject(new Error("Maximum polling time exceeded"));
      }

      try {
        const response = await fetch(apiCall);
        const data = await response.json();
        if (data && data.length > 0) {
          resolve(data[0]); // Resolve the promise when data is found
        } else {
          setTimeout(attemptFetch, retryDelay);
          retryDelay = Math.min(retryDelay * 2, 360000); // Ensure delay does not exceed 6 minutes
        }
      } catch (error) {
        reject(error); // Reject the promise on error
      }
    };

    attemptFetch(); // Initial call to start the polling
  });
}

async function fetchMessagesForBuyOrders(apiCall, signature, senderPublicKey) {
  let retryDelay = 2000; // Start with a 2-second delay
  const maxDuration = 360000 * 2; // Maximum duration set to 12 minutes
  const startTime = Date.now(); // Record the start time
  let triedChatMessage = [];
  // Promise to handle polling logic
  await new Promise((res) => {
    setTimeout(() => {
      res();
    }, 40000);
  });
  return new Promise((resolve, reject) => {
    const attemptFetch = async () => {
      if (Date.now() - startTime > maxDuration) {
        return reject(new Error("Maximum polling time exceeded"));
      }

      try {
        const response = await fetch(apiCall);
        let data = await response.json();

        data = data.filter(
          (item) => !triedChatMessage.includes(item.signature)
        );
        if (data && data.length > 0) {
          const encodedMessageObj = data[0];
          const resKeyPair = await getKeyPair();
          const parsedData = JSON.parse(resKeyPair);
          const uint8PrivateKey = Base58.decode(parsedData.privateKey);
          const uint8PublicKey = Base58.decode(parsedData.publicKey);
          const keyPair = {
            privateKey: uint8PrivateKey,
            publicKey: uint8PublicKey,
          };
          
          const decodedMessage = decryptChatMessage(
            encodedMessageObj.data,
            keyPair.privateKey,
            senderPublicKey,
            encodedMessageObj.reference
          );
          const parsedMessage = JSON.parse(decodedMessage);
          if (parsedMessage?.extra?.chatRequestSignature === signature) {
            resolve(parsedMessage);
          } else {
            triedChatMessage.push(encodedMessageObj.signature);
            setTimeout(attemptFetch, retryDelay);
            retryDelay = Math.min(retryDelay * 2, 360000); // Ensure delay does not exceed 6 minutes
          }
          // Resolve the promise when data is found
        } else {
          setTimeout(attemptFetch, retryDelay);
          retryDelay = Math.min(retryDelay * 2, 360000); // Ensure delay does not exceed 6 minutes
        }
      } catch (error) {
        reject(error); // Reject the promise on error
      }
    };

    attemptFetch(); // Initial call to start the polling
  });
}

async function listenForChatMessage({
  nodeBaseUrl,
  senderAddress,
  senderPublicKey,
  timestamp,
}) {
  try {
    let validApi = "";
    const checkIfNodeBaseUrlIsAcceptable = apiEndpoints.find(
      (item) => item === nodeBaseUrl
    );
    if (checkIfNodeBaseUrlIsAcceptable) {
      validApi = checkIfNodeBaseUrlIsAcceptable;
    } else {
      validApi = await findUsableApi();
    }
    const wallet = await getSaveWallet();
    const address = wallet.address0;
    const before = timestamp + 5000;
    const after = timestamp - 5000;
    const apiCall = `${validApi}/chat/messages?involving=${senderAddress}&involving=${address}&reverse=true&limit=1&before=${before}&after=${after}&encoding=BASE64`;
    const encodedMessageObj = await fetchMessages(apiCall);

    const resKeyPair = await getKeyPair();
    const parsedData = JSON.parse(resKeyPair);
    const uint8PrivateKey = Base58.decode(parsedData.privateKey);
    const uint8PublicKey = Base58.decode(parsedData.publicKey);
    const keyPair = {
      privateKey: uint8PrivateKey,
      publicKey: uint8PublicKey,
    };

    const decodedMessage = decryptChatMessage(
      encodedMessageObj.data,
      keyPair.privateKey,
      senderPublicKey,
      encodedMessageObj.reference
    );
    return { secretCode: decodedMessage };
  } catch (error) {
    console.error(error);
    throw new Error(error.message);
  }
}

async function listenForChatMessageForBuyOrder({
  nodeBaseUrl,
  senderAddress,
  senderPublicKey,
  signature,
}) {
  try {
    let validApi = "";
    const checkIfNodeBaseUrlIsAcceptable = apiEndpoints.find(
      (item) => item === nodeBaseUrl
    );
    if (checkIfNodeBaseUrlIsAcceptable) {
      validApi = checkIfNodeBaseUrlIsAcceptable;
    } else {
      validApi = await findUsableApi();
    }
    const wallet = await getSaveWallet();
    const address = wallet.address0;
    const before = Date.now() + 1200000;
    const after = Date.now();
    const apiCall = `${validApi}/chat/messages?involving=${senderAddress}&involving=${address}&reverse=true&limit=1&before=${before}&after=${after}&encoding=BASE64`;
    const parsedMessageObj = await fetchMessagesForBuyOrders(
      apiCall,
      signature,
      senderPublicKey
    );

    // const resKeyPair = await getKeyPair()
    //   const parsedData = JSON.parse(resKeyPair)
    //   const uint8PrivateKey = Base58.decode(parsedData.privateKey);
    //   const uint8PublicKey = Base58.decode(parsedData.publicKey);
    //   const keyPair = {
    //     privateKey: uint8PrivateKey,
    //     publicKey: uint8PublicKey
    //   };

    // const decodedMessage =  decryptChatMessage(encodedMessageObj.data, keyPair.privateKey, senderPublicKey, encodedMessageObj.reference)
    // const parsedMessage = JSON.parse(decodedMessage)
    chrome.tabs.query({}, function (tabs) {
      tabs.forEach((tab) => {
        chrome.tabs.sendMessage(tab.id, {
          type: "RESPONSE_FOR_TRADES",
          message: parsedMessageObj,
        });
      });
    });
  } catch (error) {
    console.error(error);
    throw new Error(error.message);
  }
}

async function listenForChatMessageForBuyOrderQortalRequest({
  nodeBaseUrl,
  senderAddress,
  senderPublicKey,
  signature,
}) {
  try {
    let validApi = "";
    const checkIfNodeBaseUrlIsAcceptable = apiEndpoints.find(
      (item) => item === nodeBaseUrl
    );
    if (checkIfNodeBaseUrlIsAcceptable) {
      validApi = checkIfNodeBaseUrlIsAcceptable;
    } else {
      validApi = await findUsableApi();
    }
    const wallet = await getSaveWallet();
    const address = wallet.address0;
    const before = Date.now() + 1200000;
    const after = Date.now();
    const apiCall = `${validApi}/chat/messages?involving=${senderAddress}&involving=${address}&reverse=true&limit=1&before=${before}&after=${after}&encoding=BASE64`;
    const parsedMessageObj = await fetchMessagesForBuyOrders(
      apiCall,
      signature,
      senderPublicKey
    );

    return parsedMessageObj
  } catch (error) {
    console.error(error);
    throw new Error(error.message);
  }
}

export function removeDuplicateWindow(popupUrl) {
  chrome.windows.getAll(
    { populate: true, windowTypes: ["popup"] },
    (windows) => {
      // Filter to find popups matching the specific URL
      const existingPopupsPending = windows.filter(
        (w) =>
          w.tabs &&
          w.tabs.some(
            (tab) => tab.pendingUrl && tab.pendingUrl.startsWith(popupUrl)
          )
      );
      const existingPopups = windows.filter(
        (w) =>
          w.tabs &&
          w.tabs.some((tab) => tab.url && tab.url.startsWith(popupUrl))
      );

      if (existingPopupsPending.length > 1) {
        chrome.windows.remove(
          existingPopupsPending?.[0]?.tabs?.[0]?.windowId,
          () => {}
        );
      } else if (
        existingPopupsPending.length > 0 &&
        existingPopups.length > 0
      ) {
        chrome.windows.remove(
          existingPopupsPending?.[0]?.tabs?.[0]?.windowId,
          () => {}
        );
      }
    }
  );
}

async function setChatHeads(data) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const dataString = JSON.stringify(data);
  return await new Promise((resolve, reject) => {
    chrome.storage.local.set({ [`chatheads-${address}`]: dataString }, () => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message));
      } else {
        resolve(true);
      }
    });
  });
}

async function checkLocalFunc(){
  const apiKey = await getApiKeyFromStorage()
  return !!apiKey

}

async function getTempPublish() {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const key = `tempPublish-${address}`;
  const res = await chrome.storage.local.get([key]);
  const SIX_MINUTES = 6 * 60 * 1000; // 6 minutes in milliseconds

  if (res?.[key]) {
    const parsedData = JSON.parse(res[key]);
    const currentTime = Date.now();

    // Filter through each top-level key (e.g., "announcement") and then through its nested entries
    const filteredData = Object.fromEntries(
      Object.entries(parsedData).map(([category, entries]) => {
        // Filter out entries inside each category that are older than 6 minutes
        const filteredEntries = Object.fromEntries(
          Object.entries(entries).filter(([entryKey, entryValue]) => {
            return currentTime - entryValue.timestampSaved < SIX_MINUTES;
          })
        );
        return [category, filteredEntries];
      })
    );

    if (JSON.stringify(filteredData) !== JSON.stringify(parsedData)) {
      const dataString = JSON.stringify(filteredData);
      await chrome.storage.local.set({ [key]: dataString });
    }
    return filteredData;
  } else {
    return {};
  }
}

async function saveTempPublish({ data, key }) {
  const existingTemp = await getTempPublish();
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const newTemp = {
    ...existingTemp,
    [key]: {
      ...(existingTemp[key] || {}),
      [data.identifier]: {
        data,
        timestampSaved: Date.now(),
      },
    },
  };

  const dataString = JSON.stringify(newTemp);

  return await new Promise((resolve, reject) => {
    chrome.storage.local.set({ [`tempPublish-${address}`]: dataString }, () => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message));
      } else {
        resolve(newTemp[key]);
      }
    });
  });
}

async function setChatHeadsDirect(data) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const dataString = JSON.stringify(data);
  return await new Promise((resolve, reject) => {
    chrome.storage.local.set(
      { [`chatheads-direct-${address}`]: dataString },
      () => {
        if (chrome.runtime.lastError) {
          reject(new Error(chrome.runtime.lastError.message));
        } else {
          resolve(true);
        }
      }
    );
  });
}

async function getTimestampEnterChat() {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const key = `enter-chat-timestamp-${address}`;
  const res = await chrome.storage.local.get([key]);
  if (res?.[key]) {
    const parsedData = JSON.parse(res[key]);
    return parsedData;
  } else {
    return {};
  }
}
async function getTimestampGroupAnnouncement() {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const key = `group-announcement-${address}`;
  const res = await chrome.storage.local.get([key]);
  if (res?.[key]) {
    const parsedData = JSON.parse(res[key]);
    return parsedData;
  } else {
    return {};
  }
}

async function addTimestampGroupAnnouncement({
  groupId,
  timestamp,
  seenTimestamp,
}) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const data = (await getTimestampGroupAnnouncement()) || {};
  data[groupId] = {
    notification: timestamp,
    seentimestamp: seenTimestamp ? true : false,
  };
  const dataString = JSON.stringify(data);
  return await new Promise((resolve, reject) => {
    chrome.storage.local.set(
      { [`group-announcement-${address}`]: dataString },
      () => {
        if (chrome.runtime.lastError) {
          reject(new Error(chrome.runtime.lastError.message));
        } else {
          resolve(true);
        }
      }
    );
  });
}

async function getTimestampMention() {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const key = `enter-mention-timestamp-${address}`;
  const res = await chrome.storage.local.get([key]);
  if (res?.[key]) {
    const parsedData = JSON.parse(res[key]);
    return parsedData;
  } else {
    return {};
  }
}


export async function addTimestampMention({ groupId, timestamp }) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const data = await getTimestampMention();
  data[groupId] = timestamp;
  const dataString = JSON.stringify(data);

  return await new Promise((resolve, reject) => {
    chrome.storage.local.set(
      { [`enter-mention-timestamp-${address}`]: dataString },
      () => {
        if (chrome.runtime.lastError) {
          reject(new Error(chrome.runtime.lastError.message));
        } else {
          resolve(true);
        }
      }
    );
  });
}

async function getGroupData() {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const key = `group-data-${address}`;
  const res = await chrome.storage.local.get([key]);
  if (res?.[key]) {
    const parsedData = JSON.parse(res[key]);
    return parsedData;
  } else {
    return {};
  }
}
async function getGroupDataSingle(groupId) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const key = `group-data-${address}`;
  const res = await chrome.storage.local.get([key]);
  if (res?.[key]) {
    const parsedData = JSON.parse(res[key]);
    return parsedData[groupId] || null;
  } else {
    return null;
  }
}

async function setGroupData({
  groupId,
  secretKeyData,
  secretKeyResource,
  admins,
}) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const data = (await getGroupData()) || {};
  data[groupId] = {
    timestampLastSet: Date.now(),
    admins,
    secretKeyData,
    secretKeyResource,
  };
  const dataString = JSON.stringify(data);
  return await new Promise((resolve, reject) => {
    chrome.storage.local.set({ [`group-data-${address}`]: dataString }, () => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message));
      } else {
        resolve(true);
      }
    });
  });
}

async function addTimestampEnterChat({ groupId, timestamp }) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const data = await getTimestampEnterChat();
  data[groupId] = timestamp;
  const dataString = JSON.stringify(data);
  return await new Promise((resolve, reject) => {
    chrome.storage.local.set(
      { [`enter-chat-timestamp-${address}`]: dataString },
      () => {
        if (chrome.runtime.lastError) {
          reject(new Error(chrome.runtime.lastError.message));
        } else {
          resolve(true);
        }
      }
    );
  });
}

async function notifyAdminRegenerateSecretKey({ groupName, adminAddress }) {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const name = await getNameInfo(address);
  const nameOrAddress = name || address;
  await sendChatDirect({
    directTo: adminAddress,
    typeMessage: undefined,
    chatReference: undefined,
    messageText: `<p>Member ${nameOrAddress} has requested that you regenerate the group's secret key. Group: ${groupName}</p>`,
  });
  return true;
}

async function getChatHeads() {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const key = `chatheads-${address}`;
  const res = await chrome.storage.local.get([key]);
  if (res?.[key]) {
    const parsedData = JSON.parse(res[key]);
    return parsedData;
  } else {
    throw new Error("No Chatheads saved");
  }
}

async function getChatHeadsDirect() {
  const wallet = await getSaveWallet();
  const address = wallet.address0;
  const key = `chatheads-direct-${address}`;
  const res = await chrome.storage.local.get([key]);
  if (res?.[key]) {
    const parsedData = JSON.parse(res[key]);
    return parsedData;
  } else {
    throw new Error("No Chatheads saved");
  }
}
chrome?.runtime?.onMessage.addListener((request, sender, sendResponse) => {
  if (request) {


    switch (request.action) {
      case "version":
        // Example: respond with the version
        sendResponse({ version: "1.0" });
        break;
      case "storeWalletInfo":
        chrome.storage.local.set({ walletInfo: request.wallet }, () => {
          if (chrome.runtime.lastError) {
            sendResponse({ error: chrome.runtime.lastError.message });
          } else {
            sendResponse({ result: "Data saved successfully" });
          }
        });
        break;
      case "getWalletInfo":
        getKeyPair()
          .then(() => {
            chrome.storage.local.get(["walletInfo"], (result) => {
              if (chrome.runtime.lastError) {
                sendResponse({ error: chrome.runtime.lastError.message });
              } else if (result.walletInfo) {
                sendResponse({ walletInfo: result.walletInfo, hasKeyPair: true });
              } else {
                sendResponse({ error: "No wallet info found" });
              }
            });
          })
          .catch((error) => {
            chrome.storage.local.get(["walletInfo"], (result) => {
              if (chrome.runtime.lastError) {
                sendResponse({ error: chrome.runtime.lastError.message });
              } else if (result.walletInfo) {
                sendResponse({ walletInfo: result.walletInfo, hasKeyPair: false });
              } else {
                sendResponse({ error: "Wallet not authenticated" });
              }
            });
          });

        break;
      case "validApi":
        findUsableApi()
          .then((usableApi) => {
            console.log("Usable API:", usableApi);
          })
          .catch((error) => {
            console.error(error.message);
          });
      case "name":
        getNameInfo()
          .then((name) => {
            sendResponse(name);
          })
          .catch((error) => {
            console.error(error.message);
          });
        break;
      case "userInfo":
        getUserInfo()
          .then((name) => {
            sendResponse(name);
          })
          .catch((error) => {
            sendResponse({ error: "User not authenticated" });
            console.error(error.message);
          });
        break;
      case "decryptWallet":
        {
          const { password, wallet } = request.payload;

          decryptWallet({
            password,
            wallet,
            walletVersion: wallet?.version || walletVersion,
          })
            .then((hasDecrypted) => {
              sendResponse(hasDecrypted);
            })
            .catch((error) => {
              sendResponse({ error: error?.message });
              console.error(error.message);
            });
        }

        break;
      case "balance":
        getBalanceInfo()
          .then((balance) => {
            sendResponse(balance);
          })
          .catch((error) => {
            console.error(error.message);
          });
        break;
      case "ltcBalance":
        {
          getLTCBalance()
            .then((balance) => {
              sendResponse(balance);
            })
            .catch((error) => {
              console.error(error.message);
            });
        }
        break;
      case "sendCoin":
        {
          const { receiver, password, amount } = request.payload;
          sendCoin({ receiver, password, amount })
            .then(({ res }) => {
              if (!res?.success) {
                sendResponse({ error: res?.data?.message });
                return;
              }
              sendResponse(true);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }

        break;
      case "inviteToGroup":
        {
          const { groupId, qortalAddress, inviteTime } = request.payload;
          inviteToGroup({ groupId, qortalAddress, inviteTime })
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }

        break;
      case "saveTempPublish":
        {
          const { data, key } = request.payload;
          saveTempPublish({ data, key })
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
          return true;
        }
        break;
      case "getTempPublish":
        {
          getTempPublish()
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }

        break;

      case "createGroup":
        {
          const {
            groupName,
            groupDescription,
            groupType,
            groupApprovalThreshold,
            minBlock,
            maxBlock,
          } = request.payload;
          createGroup({
            groupName,
            groupDescription,
            groupType,
            groupApprovalThreshold,
            minBlock,
            maxBlock,
          })
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }

        break;
      case "cancelInvitationToGroup":
        {
          const { groupId, qortalAddress } = request.payload;
          cancelInvitationToGroup({ groupId, qortalAddress })
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }

        break;
      case "leaveGroup":
        {
          const { groupId } = request.payload;
          leaveGroup({ groupId })
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }

        break;
      case "joinGroup":
        {
          const { groupId } = request.payload;
          joinGroup({ groupId })
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }

        break;

      case "kickFromGroup":
        {
          const { groupId, qortalAddress, rBanReason } = request.payload;
          kickFromGroup({ groupId, qortalAddress, rBanReason })
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }

        break;
      case "banFromGroup":
        {
          const { groupId, qortalAddress, rBanReason, rBanTime } =
            request.payload;
          banFromGroup({ groupId, qortalAddress, rBanReason, rBanTime })
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }

        break;
      case "addDataPublishes":
        {
          const { data, groupId, type } = request.payload;
          addDataPublishes(data, groupId, type)
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }
        break;
      case "getDataPublishes":
        {
          const { groupId, type } = request.payload;
          getDataPublishes(groupId, type)
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }
        break;
      case "addUserSettings":
        {
          const { keyValue } = request.payload;
          addUserSettings({keyValue})
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }
        break;
      case "getUserSettings":
        {
          const { key } = request.payload;
          getUserSettings({key})
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }
        break;
      case "cancelBan":
        {
          const { groupId, qortalAddress } = request.payload;
          cancelBan({ groupId, qortalAddress })
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }

        break;
      case "registerName":
        {
          const { name } = request.payload;
          registerName({ name })
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }

        break;
        case "addTimestampMention": {
          const { groupId, timestamp } = request.payload;
          addTimestampMention({ groupId, timestamp })
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }
          break;
        case "getTimestampMention": {
          getTimestampMention()
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }
          break;
      case "makeAdmin":
        {
          const { groupId, qortalAddress } = request.payload;
          makeAdmin({ groupId, qortalAddress })
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }

        break;
      case "removeAdmin":
        {
          const { groupId, qortalAddress } = request.payload;
          removeAdmin({ groupId, qortalAddress })
            .then((res) => {
              sendResponse(res);
            })
            .catch((error) => {
              sendResponse({ error: error.message });
              console.error(error.message);
            });
        }

        break;

        case "listActions":
          {
            const data = request.payload;
            listActionsCase(data)
              .then((res) => {
                sendResponse(res);
              })
              .catch((error) => {
                sendResponse({ error: error.message });
                console.error(error.message);
              });
          }
  
          break;

      case "oauth": {
        const { nodeBaseUrl, senderAddress, senderPublicKey, timestamp } =
          request.payload;

        listenForChatMessage({
          nodeBaseUrl,
          senderAddress,
          senderPublicKey,
          timestamp,
        })
          .then(({ secretCode }) => {
            sendResponse(secretCode);
          })
          .catch((error) => {
            sendResponse({ error: error.message });
            console.error(error.message);
          });

        break;
      }
      case "setChatHeads": {
        const { data } = request.payload;

        setChatHeads({
          data,
        })
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error.message });
            console.error(error.message);
          });

        break;
      }
      case "getChatHeads": {
        getChatHeads()
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error.message });
            console.error(error.message);
          });

        break;
      }
      case "notification": {
        const notificationId = "chat_notification_" + Date.now(); // Create a unique ID

        const {} = request.payload;
        chrome.notifications.create(notificationId, {
          type: "basic",
          iconUrl: "qort.png", // Add an appropriate icon for chat notifications
          title: "New Group Message!",
          message: "You have received a new message from one of your groups",
          priority: 2, // Use the maximum priority to ensure it's noticeable
          // buttons: [
          //   { title: 'Go to group' }
          // ]
        });
        // Set a timeout to clear the notification after 'timeout' milliseconds
        setTimeout(() => {
          chrome.notifications.clear(notificationId);
        }, 3000);
        sendResponse(true);
        break;
      }
      case "addTimestampEnterChat": {
        const { groupId, timestamp } = request.payload;
        addTimestampEnterChat({ groupId, timestamp })
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error.message });
            console.error(error.message);
          });
        break;
      }

      case "setApiKey": {
        const { payload } = request;

        // Save the apiKey in chrome.storage.local for persistence
        chrome.storage.local.set({ apiKey: payload }, () => {
          sendResponse(true);
        });
        return true;
        break;
      }
      case "setCustomNodes": {
        const { nodes } = request;

        // Save the customNodes in chrome.storage.local for persistence
        chrome.storage.local.set({ customNodes: nodes }, () => {
          sendResponse(true);
        });
        return true;
        break;
      }
      case "getApiKey": {
        getApiKeyFromStorage()
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error.message });
            console.error(error.message);
          });
        return true;
        break;
      }
      case "getCustomNodesFromStorage": {
        getCustomNodesFromStorage()
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error.message });
            console.error(error.message);
          });
        return true;
        break;
      }
      
      case "notifyAdminRegenerateSecretKey": {
        const { groupName, adminAddress } = request.payload;
        notifyAdminRegenerateSecretKey({ groupName, adminAddress })
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error.message });
            console.error(error.message);
          });
        break;
      }

      case "addGroupNotificationTimestamp": {
        const { groupId, timestamp } = request.payload;
        addTimestampGroupAnnouncement({
          groupId,
          timestamp,
          seenTimestamp: true,
        })
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error.message });
            console.error(error.message);
          });
        break;
      }
      case "clearAllNotifications": {
        clearAllNotifications()
          .then((res) => {})
          .catch((error) => {});
        break;
      }
      case "setGroupData": {
        const { groupId, secretKeyData, secretKeyResource, admins } =
          request.payload;
        setGroupData({
          groupId,
          secretKeyData,
          secretKeyResource,
          admins,
        })
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error.message });
            console.error(error.message);
          });
        break;
      }
      case "getGroupDataSingle": {
        const { groupId } = request.payload;
        getGroupDataSingle(groupId)
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error.message });
            console.error(error.message);
          });
        return true;
        break;
      }
      case "getTimestampEnterChat": {
        getTimestampEnterChat()
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error.message });
            console.error(error.message);
          });
        break;
      }
      case "getGroupNotificationTimestamp": {
        getTimestampGroupAnnouncement()
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error.message });
            console.error(error.message);
          });
        break;
      }
      case "authentication":
        {
          getSaveWallet()
            .then(() => {
              sendResponse(true);
            })
            .catch((error) => {
              const popupUrl = chrome.runtime.getURL(
                "index.html?secondary=true"
              );

              chrome.windows.getAll(
                { populate: true, windowTypes: ["popup"] },
                (windows) => {
                  // Attempt to find an existing popup window that has a tab with the correct URL
                  const existingPopup = windows.find(
                    (w) =>
                      w.tabs &&
                      w.tabs.some(
                        (tab) => tab.url && tab.url.startsWith(popupUrl)
                      )
                  );
                  if (existingPopup) {
                    // If the popup exists but is minimized or not focused, focus it
                    chrome.windows.update(existingPopup.id, {
                      focused: true,
                      state: "normal",
                    });
                  } else {
                    // No existing popup found, create a new one
                    chrome.system.display.getInfo((displays) => {
                      // Assuming the primary display is the first one (adjust logic as needed)
                      const primaryDisplay = displays[0];
                      const screenWidth = primaryDisplay.bounds.width;
                      const windowHeight = 650; // Your window height
                      const windowWidth = 400; // Your window width

                      // Calculate left position for the window to appear on the right of the screen
                      const leftPosition = screenWidth - windowWidth;

                      // Calculate top position for the window, adjust as desired
                      const topPosition =
                        (primaryDisplay.bounds.height - windowHeight) / 2;

                      chrome.windows.create(
                        {
                          url: chrome.runtime.getURL(
                            "index.html?secondary=true"
                          ),
                          type: "popup",
                          width: windowWidth,
                          height: windowHeight,
                          left: leftPosition,
                          top: 0,
                        },
                        () => {
                          removeDuplicateWindow(popupUrl);
                        }
                      );
                    });
                  }

                  const interactionId = Date.now().toString(); // Simple example; consider a better unique ID

                  setTimeout(() => {
                    chrome.runtime.sendMessage({
                      action: "SET_COUNTDOWN",
                      payload: request.timeout ? 0.75 * request.timeout : 60,
                    });
                    chrome.runtime.sendMessage({
                      action: "UPDATE_STATE_REQUEST_AUTHENTICATION",
                      payload: {
                        hostname,
                        interactionId,
                      },
                    });
                  }, 500);

                  // Store sendResponse callback with the interaction ID
                  pendingResponses.set(interactionId, sendResponse);
                  let intervalId = null;
                  const startTime = Date.now();
                  const checkInterval = 3000; // Check every 3 seconds
                  const timeout = request.timeout
                    ? 0.75 * (request.timeout * 1000)
                    : 60000; // Stop after 15 seconds

                  const checkFunction = () => {
                    getSaveWallet()
                      .then(() => {
                        clearInterval(intervalId); // Stop checking
                        sendResponse(true); // Perform the success action
                        chrome.runtime.sendMessage({
                          action: "closePopup",
                        });
                      })
                      .catch((error) => {
                        // Handle error if needed
                      });

                    if (Date.now() - startTime > timeout) {
                      sendResponse({
                        error: "User has not authenticated, try again.",
                      });
                      clearInterval(intervalId); // Stop checking due to timeout

                      // Handle timeout situation if needed
                    }
                  };

                  intervalId = setInterval(checkFunction, checkInterval);
                }
              );
            });
        }
        break;
      case "buyOrder":
        {
          const { qortalAtAddresses, hostname, useLocal } = request.payload;
          getTradesInfo(qortalAtAddresses)
            .then((crosschainAtInfo) => {
              const popupUrl = chrome.runtime.getURL(
                "index.html?secondary=true"
              );

              chrome.windows.getAll(
                { populate: true, windowTypes: ["popup"] },
                (windows) => {
                  // Attempt to find an existing popup window that has a tab with the correct URL
                  const existingPopup = windows.find(
                    (w) =>
                      w.tabs &&
                      w.tabs.some(
                        (tab) => tab.url && tab.url.startsWith(popupUrl)
                      )
                  );
                  if (existingPopup) {
                    // If the popup exists but is minimized or not focused, focus it
                    chrome.windows.update(existingPopup.id, {
                      focused: true,
                      state: "normal",
                    });
                  } else {
                    // No existing popup found, create a new one
                    chrome.system.display.getInfo((displays) => {
                      // Assuming the primary display is the first one (adjust logic as needed)
                      const primaryDisplay = displays[0];
                      const screenWidth = primaryDisplay.bounds.width;
                      const windowHeight = 650; // Your window height
                      const windowWidth = 400; // Your window width

                      // Calculate left position for the window to appear on the right of the screen
                      const leftPosition = screenWidth - windowWidth;

                      // Calculate top position for the window, adjust as desired
                      const topPosition =
                        (primaryDisplay.bounds.height - windowHeight) / 2;

                      chrome.windows.create(
                        {
                          url: chrome.runtime.getURL(
                            "index.html?secondary=true"
                          ),
                          type: "popup",
                          width: windowWidth,
                          height: windowHeight,
                          left: leftPosition,
                          top: 0,
                        },
                        () => {
                          removeDuplicateWindow(popupUrl);
                        }
                      );
                    });
                  }

                  const interactionId = Date.now().toString(); // Simple example; consider a better unique ID

                  setTimeout(() => {
                    chrome.runtime.sendMessage({
                      action: "SET_COUNTDOWN",
                      payload: request.timeout ? 0.9 * request.timeout : 20,
                    });
                    chrome.runtime.sendMessage({
                      action: "UPDATE_STATE_REQUEST_BUY_ORDER",
                      payload: {
                        hostname,
                        crosschainAtInfo,
                        interactionId,
                        useLocal
                      },
                    });
                  }, 500);

                  // Store sendResponse callback with the interaction ID
                  pendingResponses.set(interactionId, sendResponse);
                }
              );
            })
            .catch((error) => {
              console.error(error.message);
            });
        }

        break;
      case "connection":
        {
          const { hostname } = request.payload;

          connection(hostname)
            .then((isConnected) => {
              if (
                Object.keys(isConnected)?.length > 0 &&
                isConnected[hostname]
              ) {
                sendResponse(true);
              } else {
                const popupUrl = chrome.runtime.getURL(
                  "index.html?secondary=true"
                );
                chrome.windows.getAll(
                  { populate: true, windowTypes: ["popup"] },
                  (windows) => {
                    // Attempt to find an existing popup window that has a tab with the correct URL
                    const existingPopup = windows.find(
                      (w) =>
                        w.tabs &&
                        w.tabs.some(
                          (tab) => tab.url && tab.url.startsWith(popupUrl)
                        )
                    );

                    if (existingPopup) {
                      // If the popup exists but is minimized or not focused, focus it
                      chrome.windows.update(existingPopup.id, {
                        focused: true,
                        state: "normal",
                      });
                    } else {
                      // No existing popup found, create a new one
                      chrome.system.display.getInfo((displays) => {
                        // Assuming the primary display is the first one (adjust logic as needed)
                        const primaryDisplay = displays[0];
                        const screenWidth = primaryDisplay.bounds.width;
                        const windowHeight = 650; // Your window height
                        const windowWidth = 400; // Your window width

                        // Calculate left position for the window to appear on the right of the screen
                        const leftPosition = screenWidth - windowWidth;

                        // Calculate top position for the window, adjust as desired
                        const topPosition =
                          (primaryDisplay.bounds.height - windowHeight) / 2;

                        chrome.windows.create(
                          {
                            url: popupUrl,
                            type: "popup",
                            width: windowWidth,
                            height: windowHeight,
                            left: leftPosition,
                            top: 0,
                          },
                          () => {
                            removeDuplicateWindow(popupUrl);
                          }
                        );
                      });
                    }

                    const interactionId = Date.now().toString(); // Simple example; consider a better unique ID

                    setTimeout(() => {
                      chrome.runtime.sendMessage({
                        action: "SET_COUNTDOWN",
                        payload: request.timeout ? 0.9 * request.timeout : 20,
                      });
                      chrome.runtime.sendMessage({
                        action: "UPDATE_STATE_REQUEST_CONNECTION",
                        payload: {
                          hostname,
                          interactionId,
                        },
                      });
                    }, 500);

                    // Store sendResponse callback with the interaction ID
                    pendingResponses.set(interactionId, sendResponse);
                  }
                );
              }
            })
            .catch((error) => {
              console.error(error.message);
            });
        }

        break;
      case "sendQort":
        {
          const { amount, hostname, address, description } = request.payload;
          const popupUrl = chrome.runtime.getURL("index.html?secondary=true");

          chrome.windows.getAll(
            { populate: true, windowTypes: ["popup"] },
            (windows) => {
              // Attempt to find an existing popup window that has a tab with the correct URL
              const existingPopup = windows.find(
                (w) =>
                  w.tabs &&
                  w.tabs.some((tab) => tab.url && tab.url.startsWith(popupUrl))
              );
              if (existingPopup) {
                // If the popup exists but is minimized or not focused, focus it
                chrome.windows.update(existingPopup.id, {
                  focused: true,
                  state: "normal",
                });
              } else {
                // No existing popup found, create a new one
                chrome.system.display.getInfo((displays) => {
                  // Assuming the primary display is the first one (adjust logic as needed)
                  const primaryDisplay = displays[0];
                  const screenWidth = primaryDisplay.bounds.width;
                  const windowHeight = 650; // Your window height
                  const windowWidth = 400; // Your window width

                  // Calculate left position for the window to appear on the right of the screen
                  const leftPosition = screenWidth - windowWidth;

                  // Calculate top position for the window, adjust as desired
                  const topPosition =
                    (primaryDisplay.bounds.height - windowHeight) / 2;

                  chrome.windows.create(
                    {
                      url: chrome.runtime.getURL("index.html?secondary=true"),
                      type: "popup",
                      width: windowWidth,
                      height: windowHeight,
                      left: leftPosition,
                      top: 0,
                    },
                    () => {
                      removeDuplicateWindow(popupUrl);
                    }
                  );
                });
              }

              const interactionId = Date.now().toString(); // Simple example; consider a better unique ID

              setTimeout(() => {
                chrome.runtime.sendMessage({
                  action: "SET_COUNTDOWN",
                  payload: (request.timeout ? request.timeout : 60) - 6,
                });
                chrome.runtime.sendMessage({
                  action: "UPDATE_STATE_CONFIRM_SEND_QORT",
                  payload: {
                    amount,
                    address,
                    hostname,
                    description,
                    interactionId,
                  },
                });
              }, 500);

              // Store sendResponse callback with the interaction ID
              pendingResponses.set(interactionId, sendResponse);
            }
          );
        }

        break;
      case "responseToConnectionRequest":
        {
          const { hostname, isOkay } = request.payload;
          const interactionId3 = request.payload.interactionId;
          if (!isOkay) {
            const originalSendResponse = pendingResponses.get(interactionId3);
            if (originalSendResponse) {
              originalSendResponse(false);
              sendResponse(false);
            }
          } else {
            const originalSendResponse = pendingResponses.get(interactionId3);
            if (originalSendResponse) {
              // Example of setting domain permission
              chrome.storage.local.set({ [hostname]: true });

              originalSendResponse(true);
              sendResponse(true);
            }
          }

          pendingResponses.delete(interactionId3);
        }

        break;
      case "sendQortConfirmation":
        const { password, amount, receiver, isDecline } = request.payload;
        const interactionId2 = request.payload.interactionId;
        // Retrieve the stored sendResponse callback
        const originalSendResponse = pendingResponses.get(interactionId2);

        if (originalSendResponse) {
          if (isDecline) {
            originalSendResponse({ error: "User has declined" });
            sendResponse(false);
            pendingResponses.delete(interactionId2);
            return;
          }
          sendCoin({ password, amount, receiver }, true)
            .then((res) => {
              sendResponse(true);
              // Use the sendResponse callback to respond to the original message
              originalSendResponse(res);
              // Remove the callback from the Map as it's no longer needed
              pendingResponses.delete(interactionId2);
              // chrome.runtime.sendMessage({
              //   action: "closePopup",
              // });
            })
            .catch((error) => {
              console.error(error.message);
              sendResponse({ error: error.message });
              originalSendResponse({ error: error.message });
            });
        }

        break;
      case "buyOrderConfirmation":
        {
          const { crosschainAtInfo, isDecline, useLocal } = request.payload;
          const interactionId2 = request.payload.interactionId;
          // Retrieve the stored sendResponse callback
          const originalSendResponse = pendingResponses.get(interactionId2);

          if (originalSendResponse) {
            if (isDecline) {
              originalSendResponse({ error: "User has declined" });
              sendResponse(false);
              pendingResponses.delete(interactionId2);
              return;
            }
            createBuyOrderTx({ crosschainAtInfo, useLocal })
              .then((res) => {
                sendResponse(true);
                originalSendResponse(res);
                pendingResponses.delete(interactionId2);
              })
              .catch((error) => {
                console.error(error.message);
                sendResponse({ error: error.message });
                // originalSendResponse({ error: error.message });
              });
          }
        }

        break;
      case "encryptAndPublishSymmetricKeyGroupChat": {
        const { groupId, previousData, previousNumber } = request.payload;

        encryptAndPublishSymmetricKeyGroupChat({
          groupId,
          previousData,
          previousNumber,
        })
          .then(({ data, numberOfMembers }) => {
            sendResponse(data);

            if (!previousData) {
              // first secret key of the group
              sendChatGroup({
                groupId,
                typeMessage: undefined,
                chatReference: undefined,
                messageText: PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY,
              })
                .then(() => {})
                .catch((error) => {
                  console.error("1", error.message);
                });
              return;
            }
            sendChatNotification(data, groupId, previousData, numberOfMembers);
          })
          .catch((error) => {
            console.error(error.message);
            sendResponse({ error: error.message });
          });

        break;
      }
      case "encryptAndPublishSymmetricKeyGroupChatForAdmins": {
        const { groupId, previousData, admins } = request.payload;

        encryptAndPublishSymmetricKeyGroupChatForAdmins({
          groupId, previousData, admins
        })
          .then((data) => {
            sendResponse(data);
          })
          .catch((error) => {
            console.error(error.message);
            sendResponse({ error: error.message });
          });
        return true;
        break;
      }
      case "publishGroupEncryptedResource": {
        const { encryptedData, identifier } = request.payload;

        publishGroupEncryptedResource({
          encryptedData,
          identifier,
        })
          .then((data) => {
            sendResponse(data);
          })
          .catch((error) => {
            console.error(error.message);
            sendResponse({ error: error.message });
          });
        return true;
        break;
      }
      case "publishOnQDN": {
        const { data, identifier, service, title,
          description,
          category,
          tag1,
          tag2,
          tag3,
          tag4,
          tag5, uploadType } = request.payload;

        publishOnQDN({
          data,
          identifier,
          service,
          title,
          description,
          category,
          tag1,
          tag2,
          tag3,
          tag4,
          tag5,
          uploadType
        })
          .then((data) => {
            sendResponse(data);
          })
          .catch((error) => {
            console.error(error?.message);
            sendResponse({ error: error?.message || 'Unable to publish' });
          });
        return true;
        break;
      }
      case "handleActiveGroupDataFromSocket": {
        const { groups, directs } = request.payload;
        handleActiveGroupDataFromSocket({
          groups,
          directs,
        })
          .then((data) => {
            sendResponse(true);
          })
          .catch((error) => {
            console.error(error.message);
            sendResponse({ error: error.message });
          });

        break;
      }
      case "getThreadActivity": {
        checkThreads(true)
          .then((data) => {
            sendResponse(data);
          })
          .catch((error) => {
            console.error(error.message);
            sendResponse({ error: error.message });
          });

        break;
      }

      case "updateThreadActivity": {
        const { threadId, qortalName, groupId, thread } = request.payload;

        updateThreadActivity({ threadId, qortalName, groupId, thread })
          .then(() => {
            sendResponse(true);
          })
          .catch((error) => {
            console.error(error.message);
            sendResponse({ error: error.message });
          });

        break;
      }
      case "decryptGroupEncryption": {
        const { data } = request.payload;

        decryptGroupEncryption({ data })
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            console.error(error.message);
            sendResponse({ error: error.message });
          });

        break;
      }
      case "encryptSingle": {
        const { data, secretKeyObject, typeNumber } = request.payload;

        encryptSingle({ data64: data, secretKeyObject: secretKeyObject, typeNumber })
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            console.error(error.message);
            sendResponse({ error: error.message });
          });

        break;
      }
      case "decryptSingle": {
        const { data, secretKeyObject, skipDecodeBase64 } = request.payload;

        decryptSingleFunc({ messages: data, secretKeyObject, skipDecodeBase64 })
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            console.error(error.message);
            sendResponse({ error: error.message });
          });

        break;
      }
      case "pauseAllQueues": {
        pauseAllQueues();
        sendResponse(true);

        break;
      
      }
      case "resumeAllQueues": {
        resumeAllQueues();
        sendResponse(true);

        break;
      }
      case "checkLocal": {
        checkLocalFunc()
        .then((res) => {
          sendResponse(res);
        })
        .catch((error) => {
          console.error(error.message);
          sendResponse({ error: error.message });
        });
     

        break;
      }
      case "decryptSingleForPublishes": {
        const { data, secretKeyObject, skipDecodeBase64 } = request.payload;

        decryptSingleForPublishes({
          messages: data,
          secretKeyObject,
          skipDecodeBase64,
        })
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            console.error(error.message);
            sendResponse({ error: error.message });
          });

        break;
      }

      case "decryptDirect": {
        const { data, involvingAddress } = request.payload;

        decryptDirectFunc({ messages: data, involvingAddress })
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            console.error(error.message);
            sendResponse({ error: error.message });
          });

        break;
      }

      case "sendChatGroup": {
        const {
          groupId,
          typeMessage = undefined,
          chatReference = undefined,
          messageText,
        } = request.payload;

        sendChatGroup({ groupId, typeMessage, chatReference, messageText })
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            console.error(error.message);
            sendResponse({ error: error.message });
          });

        break;
      }
      case "sendChatDirect": {
        const {
          directTo,
          typeMessage = undefined,
          chatReference = undefined,
          messageText,
          publicKeyOfRecipient,
          address,
          otherData
        } = request.payload;
        
        sendChatDirect({
          directTo,
          chatReference,
          messageText,
          typeMessage,
          publicKeyOfRecipient,
          address,
          otherData
        })
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            console.error(error.message);
            sendResponse({ error: error.message });
          });

        break;
      }
      case "setupGroupWebsocket": {
        checkNewMessages();
        checkThreads();

        // if(socket){
        //   if(groups){
        //     console.log('hasgroups1')
        //     chrome.runtime.sendMessage({
        //       action: "SET_GROUPS",
        //       payload: groups,
        //     });
        //   }
        //   if(directs){
        //     console.log('hasgroups1')
        //     chrome.runtime.sendMessage({
        //       action: "SET_DIRECTS",
        //       payload: directs,
        //     });
        //   }
        //   sendResponse(true)
        //   return
        // }
        // setTimeout(() => {
        //   // initWebsocketMessageGroup()
        //   listenForNewGroupAnnouncements()
        //   listenForThreadUpdates()
        // }, 200);
        sendResponse(true);

        break;
      }
      case "createRewardShare": {
        const {
          recipientPublicKey
        } = request.payload;     
        createRewardShareCase({recipientPublicKey})
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error?.message });
          });

        break;
      }
      case "getRewardSharePrivateKey": {     
        const {
          recipientPublicKey
        } = request.payload;  
        getRewardSharePrivateKeyCase({recipientPublicKey})
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error?.message });
          });

        break;
      }
      case "removeRewardShare": {     
        const {
          rewardShareKeyPairPublicKey, recipient, percentageShare
        } = request.payload;     
        removeRewardShareCase({rewardShareKeyPairPublicKey, recipient, percentageShare})
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error?.message });
          });

        break;
      }

      case "addEnteredQmailTimestamp": {     
        addEnteredQmailTimestamp()
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error?.message });
          });

        break;
      }
      case "getEnteredQmailTimestamp": {     
        getEnteredQmailTimestamp()
          .then((res) => {
            sendResponse(res);
          })
          .catch((error) => {
            sendResponse({ error: error?.message });
          });

        break;
      }
      case "logout":
        {
          try {
            const logoutFunc = async () => {
              forceCloseWebSocket();
              clearAllQueues();
              if (interval) {
                // for announcement notification
                clearInterval(interval);
              }
              groupSecretkeys = {}
              const wallet = await getSaveWallet();
              const address = wallet.address0;
              const key1 = `tempPublish-${address}`;
              const key2 = `group-data-${address}`;
              const key3 = `${address}-publishData`;
              chrome.storage.local.remove(
                [
                  "keyPair",
                  "walletInfo",
                  "active-groups-directs",
                  key1,
                  key2,
                  key3,
                ],
                () => {
                  if (chrome.runtime.lastError) {
                    // Handle error
                    console.error(chrome.runtime.lastError.message);
                  } else {
                    chrome.tabs.query({}, function (tabs) {
                      tabs.forEach((tab) => {
                        chrome.tabs.sendMessage(tab.id, { type: "LOGOUT" });
                      });
                    });
                    // Data removed successfully
                    sendResponse(true);
                  }
                }
              );
            };
            logoutFunc();
          } catch (error) {}
        }

        break;
    }
  }
  return true;
});

// Function to save window position and size
const saveWindowBounds = (windowId) => {
  chrome.windows.get(windowId, (window) => {
    const { top, left, width, height } = window;
    chrome.storage.local.set(
      {
        windowBounds: { top, left, width, height },
      },
      () => {
        console.log("Window bounds saved:", { top, left, width, height });
      }
    );
  });
};

// Function to restore window position and size
const restoreWindowBounds = (callback) => {
  chrome.storage.local.get("windowBounds", (data) => {
    if (data.windowBounds) {
      callback(data.windowBounds);
    } else {
      callback(null); // No saved bounds, use default size/position
    }
  });
};

chrome.action?.onClicked?.addListener((tab) => {
  const popupUrl = chrome.runtime.getURL("index.html?main=true");
  chrome.windows.getAll(
    { populate: true, windowTypes: ["popup"] },
    (windows) => {
      // Attempt to find an existing popup window that has a tab with the correct URL
      const existingPopup = windows.find((w) => {
        return (
          w.tabs &&
          w.tabs.some((tab) => tab.url && tab.url.startsWith(popupUrl))
        );
      });
      if (existingPopup) {
        // If the popup exists but is minimized or not focused, focus it

        if (isMobile) {
          const correctTab = existingPopup.tabs.find(
            (tab) => tab.url && tab.url.startsWith(popupUrl)
          );
          if (correctTab) {
            chrome.tabs.update(correctTab.id, { active: true });
            chrome.windows.update(existingPopup.id, {
              focused: true,
              state: "normal",
            });
          }
        } else {
          chrome.windows.update(existingPopup.id, {
            focused: true,
            state: "normal",
          });
        }
      } else {
        // No existing popup found, restore the saved bounds or create a new one
        restoreWindowBounds((savedBounds) => {
          chrome.system.display.getInfo((displays) => {
            // Assuming the primary display is the first one (adjust logic as needed)
            const primaryDisplay = displays[0];
            const screenWidth = primaryDisplay.bounds.width;
            const screenHeight = primaryDisplay.bounds.height;

            // Create a new window that uses the saved bounds if available
            chrome.windows.create(
              {
                url: chrome.runtime.getURL("index.html?main=true"),
                type: "popup",
                width: savedBounds ? savedBounds.width : screenWidth,
                height: savedBounds ? savedBounds.height : screenHeight,
                left: savedBounds ? savedBounds.left : 0,
                top: savedBounds ? savedBounds.top : 0,
              },
              (newWindow) => {
                // Listen for changes in the window's size or position and save them
                chrome.windows.onBoundsChanged.addListener((window) => {
                  if (window.id === newWindow.id) {
                    saveWindowBounds(newWindow.id);
                  }
                });

                // Save the final window bounds when the window is closed
                chrome.windows.onRemoved.addListener((windowId) => {
                  if (windowId === newWindow.id) {
                    saveWindowBounds(windowId); // Save the position/size before it’s closed
                  }
                });
              }
            );
          });
        });
      }

      const interactionId = Date.now().toString(); // Simple example; consider a better unique ID

      setTimeout(() => {
        chrome.runtime.sendMessage({
          action: "INITIATE_MAIN",
          payload: {},
        });
      }, 500);

      // Store sendResponse callback with the interaction ID
      pendingResponses.set(interactionId, sendResponse);
    }
  );
});

const checkGroupList = async () => {
  try {
    const wallet = await getSaveWallet();
    const address = wallet.address0;
    const url = await createEndpoint(`/chat/active/${address}`);
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
      },
    });
    const data = await response.json();

    const copyGroups = [...(data?.groups || [])]
              const findIndex = copyGroups?.findIndex(item => item?.groupId === 0)
              if(findIndex !== -1){
                copyGroups[findIndex] = {
                  ...(copyGroups[findIndex] || {}),
                  groupId: "0"
                }
              }
              const filteredGroups = copyGroups
    const sortedGroups = filteredGroups.sort(
      (a, b) => (b.timestamp || 0) - (a.timestamp || 0)
    );
    const sortedDirects = (data?.direct || [])
      .filter(
        (item) =>
          item?.name !== "extension-proxy" &&
          item?.address !== "QSMMGSgysEuqDCuLw3S4cHrQkBrh3vP3VH"
      )
      .sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));

    handleActiveGroupDataFromSocket({
      groups: sortedGroups,
      directs: sortedDirects,
    });
  } catch (error) {
    console.error(error);
  } finally {
  }
};

const checkActiveChatsForNotifications = async () => {
  try {
    const popupUrl = chrome.runtime.getURL("index.html?main=true");

    chrome.windows.getAll(
      { populate: true, windowTypes: ["popup"] },
      (windows) => {
        // Attempt to find an existing popup window that has a tab with the correct URL
        const existingPopup = windows.find((w) => {
          return (
            w.tabs &&
            w.tabs.some((tab) => tab.url && tab.url.startsWith(popupUrl))
          );
        });

        if (existingPopup) {
        } else {
          checkGroupList();
        }
      }
    );
  } catch (error) {}
};
chrome.notifications?.onClicked?.addListener((notificationId) => {
  const popupUrl = chrome.runtime.getURL("index.html?main=true");
  const isDirect = notificationId.includes("_type=direct_");
  const isGroup = notificationId.includes("_type=group_");
  const isGroupAnnouncement = notificationId.includes(
    "_type=group-announcement_"
  );
  const isNewThreadPost = notificationId.includes("_type=thread-post_");

  let isExisting = false;
  chrome.windows.getAll(
    { populate: true, windowTypes: ["popup"] },
    async (windows) => {
      // Attempt to find an existing popup window that has a tab with the correct URL
      const existingPopup = windows.find((w) => {
        return (
          w.tabs &&
          w.tabs.some((tab) => tab.url && tab.url.startsWith(popupUrl))
        );
      });

      if (existingPopup) {
        // If the popup exists but is minimized or not focused, focus it
        chrome.windows.update(existingPopup.id, {
          focused: true,
          state: "normal",
        });
        isExisting = true;
      } else {
        // No existing popup found, restore saved bounds or create a new one
        restoreWindowBounds((savedBounds) => {
          chrome.system.display.getInfo((displays) => {
            // Assuming the primary display is the first one (adjust logic as needed)
            const primaryDisplay = displays[0];
            const screenWidth = primaryDisplay.bounds.width;
            const screenHeight = primaryDisplay.bounds.height;

            // Create a new window that takes up the full screen or uses saved bounds
            chrome.windows.create(
              {
                url: chrome.runtime.getURL("index.html?main=true"),
                type: "popup",
                width: savedBounds ? savedBounds.width : screenWidth,
                height: savedBounds ? savedBounds.height : screenHeight,
                left: savedBounds ? savedBounds.left : 0,
                top: savedBounds ? savedBounds.top : 0,
              },
              (newWindow) => {
                // Listen for changes in the window's size or position and save them
                chrome.windows.onBoundsChanged.addListener((window) => {
                  if (window.id === newWindow.id) {
                    saveWindowBounds(newWindow.id);
                  }
                });

                // Save the final window bounds when the window is closed
                chrome.windows.onRemoved.addListener((windowId) => {
                  if (windowId === newWindow.id) {
                    saveWindowBounds(windowId); // Save the position/size before it’s closed
                  }
                });
              }
            );
          });
        });
      }
      const activeData = (await getStoredData("active-groups-directs")) || {
        groups: [],
        directs: [],
      };
      setTimeout(
        () => {
          chrome.runtime.sendMessage({
            action: "SET_GROUPS",
            payload: activeData?.groups || [],
          });
          chrome.runtime.sendMessage({
            action: "SET_DIRECTS",
            payload: activeData?.directs || [],
          });
        },
        isExisting ? 100 : 1000
      );
      const interactionId = Date.now().toString(); // Simple example; consider a better unique ID

      setTimeout(
        () => {
          chrome.runtime.sendMessage({
            action: "INITIATE_MAIN",
            payload: {},
          });

          // Handle different types of notifications
          if (isDirect) {
            const fromValue = notificationId.split("_from=")[1];
            chrome.runtime.sendMessage({
              action: "NOTIFICATION_OPEN_DIRECT",
              payload: { from: fromValue },
            });
          } else if (isGroup) {
            const fromValue = notificationId.split("_from=")[1];
            chrome.runtime.sendMessage({
              action: "NOTIFICATION_OPEN_GROUP",
              payload: { from: fromValue },
            });
          } else if (isGroupAnnouncement) {
            const fromValue = notificationId.split("_from=")[1];
            chrome.runtime.sendMessage({
              action: "NOTIFICATION_OPEN_ANNOUNCEMENT_GROUP",
              payload: { from: fromValue },
            });
          } else if (isNewThreadPost) {
            const dataValue = notificationId.split("_data=")[1];
            const dataParsed = JSON.parse(dataValue);

            chrome.runtime.sendMessage({
              action: "NOTIFICATION_OPEN_THREAD_NEW_POST",
              payload: { data: dataParsed },
            });
          }
        },
        isExisting ? 400 : 3000
      );

      // Store sendResponse callback with the interaction ID
      pendingResponses.set(interactionId, sendResponse);
    }
  );
});

export const checkPaymentsForNotifications = async (address) => {
  try {
    const isDisableNotifications =
    (await getUserSettings({ key: "disable-push-notifications" })) || false;
    if(isDisableNotifications) return
      let latestPayment = null
          const savedtimestamp = await getTimestampLatestPayment();

          const url = await createEndpoint(
            `/transactions/search?txType=PAYMENT&address=${address}&confirmationStatus=CONFIRMED&limit=5&reverse=true`
          );
         
         const response =   await fetch(url, {
              method: "GET",
              headers: {
                "Content-Type": "application/json",
              },
            });
        
          const responseData = await response.json();

          const latestTx = responseData.filter(
            (tx) => tx?.creatorAddress !== address && tx?.recipient === address
          )[0];
          if (!latestTx) {
            return; // continue to the next group
          }
          if (
            checkDifference(latestTx.timestamp, 86400000) &&
            (!savedtimestamp ||
              latestTx.timestamp >
                savedtimestamp)
          ) {
            if(latestTx.timestamp){
              latestPayment = latestTx
              await addTimestampLatestPayment(latestTx.timestamp);
            }
           
            // save new timestamp
          }
       

    if (
      latestPayment
    ) {
      // Create a unique notification ID with type and group announcement details
      const notificationId =
      encodeURIComponent("payment_notification_" +
        Date.now() +
        "_type=payment-announcement");

      const title = "New payment!";
      const body = `You have received a new payment of ${latestPayment?.amount} QORT`;


      chrome.notifications.create(notificationId, {
        type: "basic",
        iconUrl: "qort.png", // Add an appropriate icon for chat notifications
        title,
        message: body,
        priority: 2, // Use the maximum priority to ensure it's noticeable
        // buttons: [
        //   { title: 'Go to group' }
        // ]
      });
      if (!isMobile) {
        setTimeout(() => {
          chrome.notifications.clear(notificationId);
        }, 7000);
      }

      // Automatically close the notification after 5 seconds if it’s not clicked
      setTimeout(() => {
        notification.close();
      }, 10000); // Close after 5 seconds

      chrome.runtime.sendMessage({
        action: "SET_PAYMENT_ANNOUNCEMENT",
        payload: latestPayment,
      });
      
    }
   
  } catch (error) {
    console.error(error)
  } 
};

// Reconnect when service worker wakes up
chrome.runtime?.onStartup.addListener(() => {
  console.log("Service worker started up, reconnecting WebSocket...");
  // initWebsocketMessageGroup();
  // listenForNewGroupAnnouncements()
  // listenForThreadUpdates()
});

chrome.runtime?.onInstalled.addListener((details) => {
  if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) {
    console.log("Extension Installed");
    // Perform tasks that should only happen on extension installation
    // Example: Initialize WebSocket, set default settings, etc.
  } else if (details.reason === chrome.runtime.OnInstalledReason.UPDATE) {
    console.log("Extension Updated");
    // Handle the update logic here (e.g., migrate settings)
  } else if (
    details.reason === chrome.runtime.OnInstalledReason.CHROME_UPDATE
  ) {
    console.log("Chrome updated");
    // Optional: Handle Chrome-specific updates if necessary
  }

  // Initialize WebSocket and other required listeners
  // initWebsocketMessageGroup();
  // listenForNewGroupAnnouncements();
  // listenForThreadUpdates();
});

// Check if the alarm already exists before creating it
chrome.alarms?.get("checkForNotifications", (existingAlarm) => {
  if (!existingAlarm) {
    // If the alarm does not exist, create it
    chrome.alarms.create("checkForNotifications", { periodInMinutes: 10 });
  }
});

chrome.alarms?.get("checkForPayments", (existingAlarm) => {
  if (!existingAlarm) {
    // If the alarm does not exist, create it
    chrome.alarms.create("checkForPayments", { periodInMinutes: 3 });
  }
});

chrome.alarms?.onAlarm.addListener(async (alarm) => {
  try {
    if (alarm.name === "checkForNotifications") {
      // initWebsocketMessageGroup(address);
      const wallet = await getSaveWallet();
      const address = wallet.address0;
      if (!address) return;
      checkActiveChatsForNotifications();
      checkNewMessages();
      checkThreads();
    } else if (alarm.name === "checkForPayments") {
     
      const wallet = await getSaveWallet();
      const address = wallet.address0;
      if (!address) return;
      
      checkPaymentsForNotifications(address);
    }
  } catch (error) {}
});
