const ranges = [
    { minRange: 1, maxRange: 2, tag: "Code Card" },
    { minRange: 1, maxRange: 8, tag: "Energy" },
    { minRange: 1, maxRange: 40, tag: "Common" },
    { minRange: 1, maxRange: 40, tag: "Common" },
    { minRange: 1, maxRange: 40, tag: "Common" },
    { minRange: 1, maxRange: 40, tag: "Common" },
    { minRange: 1, maxRange: 25, tag: "Uncommon" },
    { minRange: 1, maxRange: 25, tag: "Uncommon" },
    { minRange: 1, maxRange: 25, tag: "Uncommon" },
    { minRange: 1, maxRange: 10000, tag: "slot8" },
    { minRange: 1, maxRange: 81, tag: "Reverse Holofoil" },
    { minRange: 1, maxRange: 120, tag: "Shiny Rare" },
    { minRange: 1, maxRange: 12, tag: "Shiny Ultra Rare" },
    { minRange: 1, maxRange: 10000, tag: "slot9" },
    { minRange: 1, maxRange: 81, tag: "Reverse Holofoil" },
    { minRange: 1, maxRange: 3, tag: "Illustration Rare" },
    { minRange: 1, maxRange: 8, tag: "Special Illustration Rare" },
    { minRange: 1, maxRange: 6, tag: "Hyper Rare" },
    { minRange: 1, maxRange: 1000, tag: "slot10" },
    { minRange: 1, maxRange: 16, tag: "Rare , Holofoil" },
    { minRange: 1, maxRange: 10, tag: "Double Rare" },
    { minRange: 1, maxRange: 5, tag: "Ultra Rare" }
];

async function createServerSeed() {
    const serverSeed = crypto.getRandomValues(new Uint8Array(32)).reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
    const encoder = new TextEncoder();
    const data = encoder.encode(serverSeed);
    const hashBuffer = await crypto.subtle.digest('SHA-256', data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    const serverSeedHash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    return { serverSeed, serverSeedHash };
}

function incrementNonce(nonce, offset) {
    let seconds = parseInt(nonce.slice(0, 2), 10);
    let minutes = parseInt(nonce.slice(2, 4), 10);
    let hours = parseInt(nonce.slice(4, 6), 10);
    let day = parseInt(nonce.slice(6, 8), 10);
    let month = parseInt(nonce.slice(8, 10), 10);
    let year = parseInt(nonce.slice(10, 14), 10);
    seconds += offset;
    while (seconds > 59) {
        seconds -= 60;
        minutes += 1;
        if (minutes > 59) {
            minutes = 0;
            hours += 1;
            if (hours > 23) {
                hours = 0;
                day += 1;
                const isLeapYear = (year) => (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
                const daysInMonth = [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
                if (day > daysInMonth[month - 1]) {
                    day = 1;
                    month += 1;
                    if (month > 12) {
                        month = 1;
                        year += 1;
                    }
                }
            }
        }
    }
    return (
        seconds.toString().padStart(2, '0') +
        minutes.toString().padStart(2, '0') +
        hours.toString().padStart(2, '0') +
        day.toString().padStart(2, '0') +
        month.toString().padStart(2, '0') +
        year.toString().padStart(4, '0')
    );
}

async function generateHmacSha512(key, message) {
    const keyBuffer = new TextEncoder().encode(key);
    const msgBuffer = new TextEncoder().encode(message);
    const cryptoKey = await crypto.subtle.importKey(
        "raw",
        keyBuffer,
        { name: "HMAC", hash: "SHA-512" },
        false,
        ["sign"]
    );
    const signature = await crypto.subtle.sign("HMAC", cryptoKey, msgBuffer);
    return new Uint8Array(signature);
}

async function generateRandomNumbers(ranges, serverSeed, clientSeed, nonce) {
    const randomNumbers = [];
    const numbersPerDigest = 16;
    const digestsNeeded = Math.ceil(ranges.length / numbersPerDigest);
    for (let digestIndex = 0; digestIndex < digestsNeeded; digestIndex++) {
        const message = `${clientSeed}:${nonce}:${digestIndex}`;
        const digest = await generateHmacSha512(serverSeed, message);
        let offset = 0;
        for (let i = 0; i < numbersPerDigest && randomNumbers.length < ranges.length; i++) {
            const range = ranges[randomNumbers.length];
            const bytes = digest.slice(offset, offset + 4);
            const view = new DataView(bytes.buffer, bytes.byteOffset, 4);
            const num = view.getUint32(0, false); // Big-endian
            const rand = Math.floor((num / 0x100000000) * (range.maxRange - range.minRange + 1)) + range.minRange;
            randomNumbers.push(rand);
            offset += 4;
        }
    }
    return randomNumbers;
}

async function mapTaggedResults(randomNumbers, ranges, serverSeed, clientSeed, nonce) {
    const seenValues = {};
    const taggedResults = [];
    for (let index = 0; index < randomNumbers.length; index++) {
        if (!ranges[index]) {
            console.error(`No range found for index ${index}`);
            continue;
        }
        const { tag = "UNKNOWN", minRange, maxRange } = ranges[index];
        let finalNumber = randomNumbers[index];
        let nonceOffset = 0;
        if (!seenValues[tag]) {
            seenValues[tag] = new Set();
        }
        while (seenValues[tag].has(finalNumber.toString())) {
            nonceOffset++;
            const newNonce = incrementNonce(nonce, nonceOffset);
            const message = `${clientSeed}:${newNonce}`;
            const digest = await generateHmacSha512(serverSeed, message);
            const bytes = digest.slice(0, 4);
            const view = new DataView(bytes.buffer, bytes.byteOffset, 4);
            const newNum = view.getUint32(0, false);
            finalNumber = Math.floor((newNum / 0x100000000) * (maxRange - minRange + 1)) + minRange;
        }
        seenValues[tag].add(finalNumber.toString());
        taggedResults.push({ value: finalNumber.toString(), tag });
    }
    return taggedResults;
}

async function filterResults(taggedResults, ranges) {
    let filteredResults = [];
    const usedValuesByTag = {};

    function getNextCard(tag) {
        if (!usedValuesByTag[tag]) {
            usedValuesByTag[tag] = new Set();
        }
        const cards = taggedResults.filter(item => item.tag === tag);
        for (const card of cards) {
            if (!usedValuesByTag[tag].has(card.value)) {
                usedValuesByTag[tag].add(card.value);
                return card;
            }
        }
        return null;
    }

    const add = (item) => { if (item) filteredResults.push(item); };

    add(getNextCard("Code Card"));
    add(getNextCard("Energy"));

    let commons = taggedResults.filter(item => item.tag === "Common");
    commons.slice(0, 4).forEach(item => {
        filteredResults.push(item);
    });

    let uncommons = taggedResults.filter(item => item.tag === "Uncommon");
    uncommons.slice(0, 3).forEach(item => {
        filteredResults.push(item);
    });

    const reverseHoloCategoryItem = taggedResults.find((item) => item.tag === "slot8");
        if (reverseHoloCategoryItem) {
            const reverseHoloCategoryNum = parseInt(reverseHoloCategoryItem.value, 10);
            if (reverseHoloCategoryNum >= 1 && reverseHoloCategoryNum <= 6731) {
                const reverseHolo = getNextCard("Reverse Holofoil");
                if (reverseHolo) filteredResults.push(reverseHolo);
            } else if (reverseHoloCategoryNum >= 6732 && reverseHoloCategoryNum <= 9231) {
                const shinyRare = getNextCard("Shiny Rare");
                if (shinyRare) filteredResults.push(shinyRare);
            } else if (reverseHoloCategoryNum >= 9232 && reverseHoloCategoryNum <= 10000) {
                const shinyUltraRare = getNextCard("Shiny Ultra Rare");
                if (shinyUltraRare) filteredResults.push(shinyUltraRare);
            }
        }

    const reverseHoloCategoryItem2 = taggedResults.find((item) => item.tag === "slot9");
        if (reverseHoloCategoryItem2) {
            const reverseHoloCategoryNum = parseInt(reverseHoloCategoryItem2.value, 10);
            if (reverseHoloCategoryNum >= 1 && reverseHoloCategoryNum <= 8953) {
                const reverseHolo = getNextCard("Reverse Holofoil");
                if (reverseHolo) filteredResults.push(reverseHolo);
            } else if (reverseHoloCategoryNum >= 8954 && reverseHoloCategoryNum <= 9667) {
                const illustrationRare = getNextCard("Illustration Rare");
                if (illustrationRare) filteredResults.push(illustrationRare);
            } else if (reverseHoloCategoryNum >= 9668 && reverseHoloCategoryNum <= 9839) {
                const specialIllustrationRare = getNextCard("Special Illustration Rare");
                if (specialIllustrationRare) filteredResults.push(specialIllustrationRare);
            } else if (reverseHoloCategoryNum >= 9840 && reverseHoloCategoryNum <= 10000) {
                const hyperRare = getNextCard("Hyper Rare");
                if (hyperRare) filteredResults.push(hyperRare);
            }
        }

const rareCategoryItem = taggedResults.find((item) => item.tag === "slot10");
    if (rareCategoryItem) {
        const rareCategoryNum = parseInt(rareCategoryItem.value, 10);
        if (rareCategoryNum >= 1 && rareCategoryNum <= 7666) {
            const rareCard = getNextCard("Rare , Holofoil");
            if (rareCard) filteredResults.push(rareCard);
        } else if (rareCategoryNum >= 7667 && rareCategoryNum <= 9333) {
            const rareCard = getNextCard("Double Rare");
            if (rareCard) filteredResults.push(rareCard);
        } else if (rareCategoryNum >= 9334 && rareCategoryNum <= 10000) {
            const rareCard = getNextCard("Ultra Rare");
            if (rareCard) filteredResults.push(rareCard);
        }
    }

    return filteredResults;
}

async function setData(serverSeed, clientSeed, nonce) {
    const { serverSeedHash } = await createServerSeed();
    const randomNumbers = await generateRandomNumbers(ranges, serverSeed, clientSeed, nonce);
    const taggedResults = await mapTaggedResults(randomNumbers, ranges, serverSeed, clientSeed, nonce);
    const filteredResults = await filterResults(taggedResults, ranges);
    return {
        success: true,
        randomNumbers,
        result: filteredResults,
        nonce,
        serverSeed,
        serverSeedHash,
        clientSeed
    };
}

// // Example usage with provided inputs
// (async () => {
//     try {
//         const result = await setData(
//             "78e69f8e220c3196752d26854d73134d281b6dfaadecca364548a5a079f2de2e",
//             "testUser",
//             "22542322102025"
//         );
//         console.log(JSON.stringify(result, null, 2));
//     } catch (error) {
//         console.error("Error generating pack:", error);
//     }
// })();
