// Set the forumAdminGroups variable
let adminGroups = ["Q-Mintership-admin", "dev-group", "Mintership-Forum-Admins"];

// Settings to allow non-devmode development with 'live-server' module
let baseUrl = '';
let isOutsideOfUiDevelopment = false;

if (typeof qortalRequest === 'function') {
    console.log('qortalRequest is available as a function. Setting development mode to false and baseUrl to nothing.');
    isOutsideOfUiDevelopment = false;
    baseUrl = '';
} else {
    console.log('qortalRequest is not available as a function. Setting baseUrl to localhost.');
    isOutsideOfUiDevelopment = true;
    baseUrl = "http://localhost:12391";
};

// USEFUL UTILITIES ----- ----- -----
// Generate a short random ID to utilize at the end of unique identifiers.
const uid = async () => {
    console.log('uid function called');
    const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let result = '';
    const charactersLength = characters.length;
    for (let i = 0; i < 6; i++) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
    };
    console.log('Generated uid:', result);
    return result;
};
// Turn a unix timestamp into a human-readable date
const timestampToHumanReadableDate = async(timestamp) => {
    console.log('timestampToHumanReadableDate called');
    const date = new Date(timestamp);
    const day = date.getDate();
    const month = date.getMonth() + 1;
    const year = date.getFullYear() - 2000;
    const hours = date.getHours();
    const minutes = date.getMinutes();
    const seconds = date.getSeconds();

    const formattedDate = `${month}.${day}.${year}@${hours}:${minutes}:${seconds}`;
    console.log('Formatted date:', formattedDate);
    return formattedDate;
};
// Base64 encode a string
const base64EncodeString = async (str) => {
    console.log('base64EncodeString called');
    const encodedString = btoa(String.fromCharCode.apply(null, new Uint8Array(new TextEncoder().encode(str).buffer)));
    console.log('Encoded string:', encodedString);
    return encodedString;
};

const objectToBase64 = async (obj) => {
    // Step 1: Convert the object to a JSON string
    const jsonString = JSON.stringify(obj);
    // Step 2: Create a Blob from the JSON string
    const blob = new Blob([jsonString], { type: 'application/json' });
    // Step 3: Create a FileReader to read the Blob as a base64-encoded string
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onloadend = () => {
            if (typeof reader.result === 'string') {
                // Remove 'data:application/json;base64,' prefix
                const base64 = reader.result.replace('data:application/json;base64,', '');
                console.log(`base64 resolution: ${base64}`);
                resolve(base64);
            } else {
                reject(new Error('Failed to read the Blob as a base64-encoded string'));
            }
        };
        reader.onerror = () => {
            reject(reader.error);
        };
        reader.readAsDataURL(blob);
    });
};

// User state util
const userState = {
    isLoggedIn: false,
    accountName: null,
    accountAddress: null,
    isAdmin: false,
    isMinterAdmin: false,
    isForumAdmin: false
};

// USER-RELATED QORTAL CALLS ------------------------------------------
// Obtain the address of the authenticated user checking userState.accountAddress first.
const getUserAddress = async () => {
    console.log('getUserAddress called');
    try {
        if (userState.accountAddress) {
            console.log('User address found in state:', userState.accountAddress);
            return userState.accountAddress;
        };
        const userAccount = await qortalRequest({ action: "GET_USER_ACCOUNT" });
        if (userAccount) {
            console.log('Account address:', userAccount.address);
            userState.accountAddress = userAccount.address;
            console.log('Account address added to state:', userState.accountAddress);
            return userState.accountAddress;
        };
    } catch (error) {
        console.error('Error fetching account address:', error);
        throw error;
    };
};

const fetchOwnerAddressFromName = async (name) => {
    console.log('fetchOwnerAddressFromName called');
    console.log('name:', name);
    try {
        const response = await fetch(`${baseUrl}/names/${name}`, {
            headers: { 'Accept': 'application/json' },
            method: 'GET',
        });
        const data = await response.json();
        console.log('Fetched owner address:', data.owner);
        return data.owner;
    } catch (error) {
        console.error('Error fetching owner address:', error);
        return null;
    };
};

const verifyUserIsAdmin = async () => {
    console.log('verifyUserIsAdmin called (QortalApi.js)');
    try {
        const accountAddress = userState.accountAddress || await getUserAddress();
        userState.accountAddress = accountAddress;
        const userGroups = await getUserGroups(accountAddress);
        const minterGroupAdmins = await fetchMinterGroupAdmins();
        const isAdmin = await userGroups.some(group => adminGroups.includes(group.groupName))
        const isMinterAdmin = minterGroupAdmins.members.some(admin => admin.member === userState.accountAddress && admin.isAdmin)
        if (isMinterAdmin) {
            userState.isMinterAdmin = true
        }
        if (isAdmin) {
            userState.isAdmin = true;
            userState.isForumAdmin = true
         }
        return userState.isAdmin;
    } catch (error) {
        console.error('Error verifying user admin status:', error);
        throw error;
    }
};

const verifyAddressIsAdmin = async (address) => {
    console.log('verifyAddressIsAdmin called');
    console.log('address:', address);
    try {
        if (!address) {
            console.log('No address provided');
            return false;
         };
        const userGroups = await getUserGroups(address);
        const minterGroupAdmins = await fetchMinterGroupAdmins();
        const isAdmin = await userGroups.some(group => adminGroups.includes(group.groupName))
        const isMinterAdmin = minterGroupAdmins.members.some(admin => admin.member === address && admin.isAdmin)
        if ((isMinterAdmin) || (isAdmin)) {
            return true;
          } else {
            return false
         };
     } catch (error) {
        console.error('Error verifying address admin status:', error);
        throw error
      }
};
     
const getNameInfo = async (name) => {
    console.log('getNameInfo called');
    console.log('name:', name);
    try {
        const response = await fetch(`${baseUrl}/names/${name}`);
        const data = await response.json();
        console.log('Fetched name info:', data);
        return {
            name: data.name,
            reducedName: data.reducedName,
            owner: data.owner,
            data: data.data,
            registered: data.registered,
            updated: data.updated,
            isForSale: data.isForSale,
            salePrice: data.salePrice
        };
    } catch (error) {
        console.log('Error fetching name info:', error);
        return null;
    }
};

const getPublicKeyByName = async (name) => {
    console.log('getPublicKeyByName called');
    console.log('name:', name);
    try {
        const nameInfo = await getNameInfo(name);
        const address = nameInfo.owner;
        const publicKey = await getPublicKeyFromAddress(address);
        console.log(`Found public key: for name: ${name}`, publicKey);
        return publicKey;
    } catch (error) {
        console.log('Error obtaining public key from name:', error);
        return null;
    }
};

const getPublicKeyFromAddress = async (address) => {
    console.log('getPublicKeyFromAddress called');
    console.log('address:', address);
    try {
        const response = await fetch(`${baseUrl}/addresses/${address}`,{
            method: 'GET',
            headers: { 'Accept': 'application/json' }
        });
        const data = await response.json();
        const publicKey = data.publicKey;
        console.log('Fetched public key:', publicKey);
        return publicKey;
    } catch (error) {
        console.log('Error fetching public key from address:', error);
        return null;
    }
};

const getAddressFromPublicKey = async (publicKey) => {
    console.log('getAddressFromPublicKey called');
    try {
        const response = await fetch(`${baseUrl}/addresses/convert/${publicKey}`,{
            method: 'GET',
            headers: { 'Accept': 'text/plain' }  
        });
        const data = await response.text();
        const address = data;
        console.log('Converted Address:', address);
        return address;
    } catch (error) {
        console.log('Error converting public key to address:', error);
        return null;
    }
};

const login = async () => {
    console.log('login called');
    try {
        if (userState.accountName && (userState.isAdmin || userState.isLoggedIn) && userState.accountAddress) {
            console.log(`Account name found in userState: '${userState.accountName}', no need to call API...skipping API call.`);
            return userState.accountName;
        }

        const accountAddress = userState.accountAddress || await getUserAddress();
        const accountNames = await qortalRequest({
            action: "GET_ACCOUNT_NAMES",
            address: accountAddress,
        });

        if (accountNames) {
            userState.isLoggedIn = true;
            userState.accountName = accountNames[0].name;
            userState.accountAddress = accountAddress;
            console.log('All account names:', accountNames);
            console.log('Main name (in state):', userState.accountName);
            console.log('User has been logged in successfully!');
            return userState.accountName;
        } else {
            throw new Error("No account names found. Are you logged in? Do you have a registered name?");
        }
    } catch (error) {
        console.error('Error fetching account names:', error);
        throw error;
    }
};

const getNamesFromAddress = async (address) => {
try {
    const response = await fetch(`${baseUrl}/names/address/${address}?limit=20`, {
    method: 'GET',
    headers: { 'Accept': 'application/json' }
    });
    const names = await response.json();
    return names.length > 0 ? names[0].name : address; // Return name if found, else return address
} catch (error) {
    console.error(`Error fetching names for address ${address}:`, error);
    return address;
}
};


// QORTAL GROUP-RELATED CALLS ------------------------------------------
const getUserGroups = async (userAddress) => {
    console.log('getUserGroups called');
    console.log('userAddress:', userAddress);
    try {
        if (!userAddress && userState.accountAddress) {
            console.log('No address passed to getUserGroups call... using address from state...');
            userAddress = userState.accountAddress;
        }
        const response = await fetch(`${baseUrl}/groups/member/${userAddress}`, {
            method: 'GET',
            headers: { 'accept': 'application/json' }
        });
        const data = await response.json();
        console.log('Fetched user groups:', data);
        return data;
    } catch (error) {
        console.error('Error fetching user groups:', error);
        throw error;
    }
};

const fetchMinterGroupAdmins = async () => {
    console.log('calling for minter admins')
    const response = await fetch(`${baseUrl}/groups/members/694?onlyAdmins=true&limit=0&reverse=true`,{
        method: 'GET',
        headers: { 'Accept': 'application/json' }
    });
    const admins = await response.json();
    console.log('Fetched minter admins', admins);
    return admins;
}

const fetchMinterGroupMembers = async () => {
    try {
      const response = await fetch(`${baseUrl}/groups/members/694?limit=0`, {
        method: 'GET',
        headers: { 'Accept': 'application/json' },
      });
  
      if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`);
      }
  
      const data = await response.json();
      
      // Ensure the structure of the response is as expected
      if (!Array.isArray(data.members)) {
        throw new Error("Expected 'members' to be an array but got a different structure");
      }
  
      return data.members; // Assuming 'members' is the key in the response JSON
    } catch (error) {
      console.error("Error fetching minter group members:", error);
      return []; // Return an empty array to prevent further errors
    }
  };
  
  
  

const fetchAllGroups = async (limit) => {
    console.log('fetchAllGroups called');
    console.log('limit:', limit);
    if (!limit) {
        limit = 2000;
    }
    try {
        const response = await fetch(`${baseUrl}/groups?limit=${limit}&reverse=true`);
        const data = await response.json();
        console.log('Fetched all groups:', data);
        return data;
    } catch (error) {
        console.error('Error fetching all groups:', error);
    }
};

// QDN data calls
const searchLatestDataByIdentifier = async (identifier) => {
    console.log('fetchAllDataByIdentifier called');
    console.log('identifier:', identifier);
    try {
        const response = await fetch(`${baseUrl}/arbitrary/resources/search?service=DOCUMENT&identifier=${identifier}&includestatus=true&mode=ALL&limit=0&reverse=true`, {
            method: 'GET',
            headers: { 'Accept': 'application/json' }
        });
        const latestData = await response.json();
        console.log('Fetched latest data by identifier:', latestData);
        return latestData;
    } catch (error) {
        console.error('Error fetching latest published data:', error);
        return null;
    }
};

const publishMultipleResources = async (resources, publicKeys = null, isPrivate = false) => {
    console.log('publishMultipleResources called');
    console.log('resources:', resources);

    const request = {
        action: 'PUBLISH_MULTIPLE_QDN_RESOURCES',
        resources: resources,
    };

    if (isPrivate && publicKeys) {
        request.encrypt = true;
        request.publicKeys = publicKeys;
    };

    try {
        const response = await qortalRequest(request);
        console.log('Multiple resources published successfully:', response);
    } catch (error) {
        console.error('Error publishing multiple resources:', error);
    };
};


const searchResourcesWithMetadata = async (query, limit) => {
    console.log('searchResourcesWithMetadata called');
    try {
        if (limit == 0) {
            limit = 0;
        } else if (!limit || (limit < 10 && limit != 0)) {
            limit = 200;
        }
        const response = await fetch(`${baseUrl}/arbitrary/resources/search?query=${query}&mode=ALL&includestatus=true&includemetadata=true&limit=${limit}&reverse=true`, {
            method: 'GET',
            headers: { 'accept': 'application/json' }
        });
        const data = await response.json();
        console.log('Search results with metadata:', data);
        return data;
    } catch (error) {
        console.error('Error searching for resources with metadata:', error);
        throw error;
    }
};

const searchAllResources = async (query, limit, after, reverse=false) => {
    console.log('searchAllResources called. Query:', query, 'Limit:', limit,'Reverse:', reverse);
    try {
        if (limit == 0) {
            limit = 0;
        } 
        if (!limit || (limit < 10 && limit != 0)) {
            limit = 200;
        }
        if (after == null || after === undefined) {
        after = 0;
        }
        const response = await fetch(`${baseUrl}/arbitrary/resources/search?query=${query}&mode=ALL&after=${after}&includestatus=false&includemetadata=false&limit=${limit}&reverse=${reverse}`, {
            method: 'GET',
            headers: { 'accept': 'application/json' }
        });
        const data = await response.json();
        console.log('Search results with metadata:', data);
        return data;
    } catch (error) {
        console.error('Error searching for resources with metadata:', error);
        throw error;
    }
};

const searchAllWithOffset = async (query, limit, offset) =>{
    try {
      const response = await qortalRequest({
        action: "SEARCH_QDN_RESOURCES",
        service: "BLOG_POST",
        query: query,
        limit: limit,
        offset: offset,
        mode: "ALL",
        reverse: false
      });
      return response
    } catch (error) {
      console.error("Error during SEARCH_QDN_RESOURCES:", error);
      return [];
    }
}

const searchAllCountOnly = async (query) => {
    try {
      let offset = 0;
      const limit = 100; // Chunk size for fetching
      let totalCount = 0;
      let hasMore = true;
  
      // Fetch in chunks to accumulate the count
      while (hasMore) {
        const response = await qortalRequest({
          action: "SEARCH_QDN_RESOURCES",
          service: "BLOG_POST",
          query: query,
          limit: limit,
          offset: offset,
          mode: "ALL",
          reverse: false
        });
  
        if (response && response.length > 0) {
          totalCount += response.length;
          offset += limit;
          console.log(`Fetched ${response.length} items, total count: ${totalCount}, current offset: ${offset}`);
        } else {
          hasMore = false;
        }
      }
  
      return totalCount;
    } catch (error) {
      console.error("Error during SEARCH_QDN_RESOURCES:", error);
      throw error;
    }
} 

const searchResourcesWithStatus = async (query, limit, status = 'local') => {
    console.log('searchResourcesWithStatus called');
    console.log('query:', query);
    console.log('limit:', limit);
    console.log('status:', status);
    try {
        // Set default limit if not provided or too low
        if (!limit || limit < 10) {
            limit = 200;
        }
        // Make API request
        const response = await fetch(`${baseUrl}/arbitrary/resources/search?query=${query}&includestatus=true&limit=${limit}&reverse=true`, {
            method: 'GET',
            headers: { 'accept': 'application/json' }
        });

        const data = await response.json();
        // Filter based on status if provided
        if (status) {
            if (status === 'notLocal') {
                const notDownloaded = data.filter((resource) => resource.status.status === 'published');
                console.log('notDownloaded:', notDownloaded);
                return notDownloaded;
            } else if (status === 'local') {
                const downloaded = data.filter((resource) => 
                    resource.status.status === 'ready' ||
                    resource.status.status === 'downloaded' ||
                    resource.status.status === 'building' ||
                    (resource.status.status && resource.status.status !== 'published')
                );
                return downloaded;
            }
        }
        // Return all data if no specific status is provided
        console.log('Returning all data...');
        return data;
    } catch (error) {
        console.error('Error searching for resources with metadata:', error);
        throw error;
    }
};

const getResourceMetadata = async (service, name, identifier) => {
    console.log('getResourceMetadata called');
    console.log('service:', service);
    console.log('name:', name);
    console.log('identifier:', identifier);
    try {
        const response = await fetch(`${baseUrl}/arbitrary/metadata/${service}/${name}/${identifier}`, {
            method: 'GET',
            headers: { 'accept': 'application/json' }
        });
        const data = await response.json();
        console.log('Fetched resource metadata:', data);
        return data;
    } catch (error) {
        console.error('Error fetching resource metadata:', error);
        throw error;
    }
};

const fetchFileBase64 = async (service, name, identifier) => {
    const url = `${baseUrl}/arbitrary/${service}/${name}/${identifier}/?encoding=base64`;
    try   {
        const response  = await fetch(url,{
            method: 'GET',
            headers: { 'accept': 'text/plain' }
        });
        return response;
    } catch (error) {
      console.error("Error fetching the image file:", error);
    }
};

async function loadImageHtml(service, name, identifier, filename, mimeType) {
    try {
      const url = `${baseUrl}/arbitrary/${service}/${name}/${identifier}`;
      // Fetch the file as a blob
      const response = await fetch(url);
      // Convert the response to a Blob
      const fileBlob = new Blob([response], { type: mimeType });
      // Create an Object URL from the Blob
      const objectUrl = URL.createObjectURL(fileBlob);
      // Use the Object URL as the image source
      const attachmentHtml = `<div class="attachment"><img src="${objectUrl}" alt="${filename}" class="inline-image"></div>`;
  
      return attachmentHtml;
  
    } catch (error) {
      console.error("Error fetching the image:", error);
    }
}

const fetchAndSaveAttachment = async (service, name, identifier, filename, mimeType) => {
    const url = `${baseUrl}/arbitrary/${service}/${name}/${identifier}`;
    try {
      const response = await fetch(url);
      const blob = await response.blob();
      await qortalRequest({
        action: "SAVE_FILE",
        blob,
        filename: filename,
        mimeType
      });
    } catch (error) {
      console.error("Error fetching or saving the attachment:", error);
    }
}

const renderData = async (service, name, identifier) => {
    console.log('renderData called');
    console.log('service:', service);
    console.log('name:', name);
    console.log('identifier:', identifier);

    try {
        const response = await fetch(`${baseUrl}/render/${service}/${name}?identifier=${identifier}`, {
            method: 'GET',
            headers: { 'accept': '*/*' }
        });
        // If the response is not OK (status 200-299), throw an error
        if (!response.ok) {
            throw new Error('Error rendering data');
        }

        const responseText = await response.text();
        // Check if the response includes <!DOCTYPE> indicating it's an HTML document
        if (responseText.includes('<!DOCTYPE')) {
            throw new Error('Received HTML response');
        }

        const data = JSON.parse(responseText);
        console.log('Rendered data:', data);
        return data;

    } catch (error) {
        console.error('Error rendering data:', error);
        // Return the custom message when there’s an error or invalid data
        return 'Requested data is either missing or still being obtained from QDN... please try again in a short time.';
    }
};

const getProductDetails = async (service, name, identifier) => {
    console.log('getProductDetails called');
    console.log('service:', service);
    console.log('name:', name);
    console.log('identifier:', identifier);
    try {
        const response = await fetch(`${baseUrl}/arbitrary/metadata/${service}/${name}/${identifier}`, {
            method: 'GET',
            headers: { 'accept': 'application/json' }
        });
        const data = await response.json();
        console.log('Fetched product details:', data);
        return data;
    } catch (error) {
        console.error('Error fetching product details:', error);
        throw error;
    }
};


// Qortal poll-related calls ----------------------------------------------------------------------

const fetchPollResults = async (pollName) => {
    try {
      const response = await fetch(`${baseUrl}/polls/votes/${pollName}`, {
        method: 'GET',
        headers: { 'Accept': 'application/json' }
      });
      const pollData = await response.json();
      return pollData;
    } catch (error) {
      console.error(`Error fetching poll results for ${pollName}:`, error);
      return null;
    }
  };

// export {
//     userState,
//     adminGroups,
//     searchResourcesWithMetadata,
//     searchResourcesWithStatus,
//     getResourceMetadata,
//     renderData,
//     getProductDetails,
//     getUserGroups,
//     getUserAddress,
//     login,
//     timestampToHumanReadableDate,
//     base64EncodeString,
//     verifyUserIsAdmin,
//     fetchAllDataByIdentifier,
//     fetchOwnerAddressFromName,
//     verifyAddressIsAdmin,
//     uid,
//     fetchAllGroups,
//     getNameInfo,
//     publishMultipleResources,
//     getPublicKeyByName,
//     objectToBase64,
//     fetchMinterGroupAdmins
// };