const ranges = [
    { minRange: 1, maxRange: 18, tag: "T , Foil" },
    { minRange: 1, maxRange: 88, tag: "C , Foil" },
    { minRange: 1, maxRange: 88, tag: "C , Foil" },
    { minRange: 1, maxRange: 88, tag: "C , Foil" },
    { minRange: 1, maxRange: 88, tag: "C , Foil" },
    { minRange: 1, maxRange: 104, tag: "U , Foil" },
    { minRange: 1, maxRange: 104, tag: "U , Foil" },
    { minRange: 1, maxRange: 104, tag: "U , Foil" },
    { minRange: 1, maxRange: 10, tag: "L Frame , Foil" },
    { minRange: 1, maxRange: 100, tag: "Slot10" },
    { minRange: 1, maxRange: 35, tag: "C Frame , Normal" },
    { minRange: 1, maxRange: 76, tag: "U Frame , Normal" },
    { minRange: 1, maxRange: 4, tag: "C Borderless , Normal" },
    { minRange: 1, maxRange: 4, tag: "U Borderless , Normal" },
    { minRange: 1, maxRange: 100, tag: "Slot11" },
    { minRange: 1, maxRange: 35, tag: "C Frame , Normal" },
    { minRange: 1, maxRange: 76, tag: "U Frame , Normal" },
    { minRange: 1, maxRange: 4, tag: "C Borderless , Normal" },
    { minRange: 1, maxRange: 4, tag: "U Borderless , Normal" },
    { minRange: 1, maxRange: 100, tag: "Slot12" },
    { minRange: 1, maxRange: 35, tag: "C Frame , Foil" },
    { minRange: 1, maxRange: 76, tag: "U Frame , Foil" },
    { minRange: 1, maxRange: 4, tag: "C Borderless , Foil" },
    { minRange: 1, maxRange: 4, tag: "U Borderless , Foil" },
    { minRange: 1, maxRange: 100, tag: "Slot13" },
    { minRange: 1, maxRange: 72, tag: "R , Foil" },
    { minRange: 1, maxRange: 23, tag: "M , Foil" },
    { minRange: 1, maxRange: 100, tag: "Slot14" },
    { minRange: 1, maxRange: 24, tag: "R Frame , Normal" },
    { minRange: 1, maxRange: 17, tag: "M Frame , Normal" },
    { minRange: 1, maxRange: 17, tag: "R Borderless , Normal" },
    { minRange: 1, maxRange: 6, tag: "M Borderless , Normal" },
    { minRange: 1, maxRange: 10, tag: "Poster , Normal" },
    { minRange: 1, maxRange: 100, tag: "Slot15" },
    { minRange: 1, maxRange: 24, tag: "R Frame , Normal" },
    { minRange: 1, maxRange: 17, tag: "M Frame , Normal" },
    { minRange: 1, maxRange: 17, tag: "R Borderless , Normal" },
    { minRange: 1, maxRange: 6, tag: "M Borderless , Normal" },
    { minRange: 1, maxRange: 10, tag: "Poster , Normal" },
    { minRange: 1, maxRange: 100, tag: "Slot16" },
    { minRange: 1, maxRange: 24, tag: "R Frame , Foil" },
    { minRange: 1, maxRange: 17, tag: "M Frame , Foil" },
    { minRange: 1, maxRange: 17, tag: "R Borderless , Foil" },
    { minRange: 1, maxRange: 6, tag: "M Borderless , Foil" },
    { minRange: 1, maxRange: 10, tag: "Poster , Foil" },
    { minRange: 1, maxRange: 1, tag: "Serial , Foil" },
];

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 daysInMonth = [31, 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); // Big-endian
      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, serverSeed, clientSeed, nonce) {
  let filteredResults = [];
  const usedIndexes = new Set();
  let nonceOffset = 0;

  const findAndConsume = (tag) => {
    const item = taggedResults.find((item, i) => item.tag === tag && !usedIndexes.has(i));
    if (item) usedIndexes.add(taggedResults.indexOf(item));
    return item;
  };

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

  let tFrameFoil = taggedResults.filter((item, i) => item.tag === "T , Foil" && !usedIndexes.has(i));
  tFrameFoil.slice(0, 1).forEach(item => {
    usedIndexes.add(taggedResults.indexOf(item));
    filteredResults.push(item);
  });

  let cFoil = taggedResults.filter((item, i) => item.tag === "C , Foil" && !usedIndexes.has(i));
  cFoil.slice(0, 4).forEach(item => {
    usedIndexes.add(taggedResults.indexOf(item));
    filteredResults.push(item);
  });

  let uFoil = taggedResults.filter((item, i) => item.tag === "U , Foil" && !usedIndexes.has(i));
  uFoil.slice(0, 3).forEach(item => {
    usedIndexes.add(taggedResults.indexOf(item));
    filteredResults.push(item);
  });

  let lFoil = taggedResults.filter((item, i) => item.tag === "L Frame , Foil" && !usedIndexes.has(i));
  lFoil.slice(0, 1).forEach(item => {
    usedIndexes.add(taggedResults.indexOf(item));
    filteredResults.push(item);
  });

  const slot10 = taggedResults.find(item => item.tag === "Slot10");
  let slot10Item = null;
  let slot10Tag = null;
  if (slot10) {
    const num = parseInt(slot10.value, 10);
    if (num <= 44) {
      slot10Tag = "C Frame , Normal";
      slot10Item = findAndConsume("C Frame , Normal");
    } else if (num <= 92) {
      slot10Tag = "U Frame , Normal";
      slot10Item = findAndConsume("U Frame , Normal");
    } else if (num <= 97) {
      slot10Tag = "C Borderless , Normal";
      slot10Item = findAndConsume("C Borderless , Normal");
    } else if (num <= 100) {
      slot10Tag = "U Borderless , Normal";
      slot10Item = findAndConsume("U Borderless , Normal");
    }
    add(slot10Item);
  }

  const slot11 = taggedResults.find(item => item.tag === "Slot11");
  let slot11Item = null;
  let slot11Tag = null;
  if (slot11) {
    const num = parseInt(slot11.value, 10);
    if (num <= 44) {
      slot11Tag = "C Frame , Normal";
      slot11Item = findAndConsume("C Frame , Normal");
    } else if (num <= 92) {
      slot11Tag = "U Frame , Normal";
      slot11Item = findAndConsume("U Frame , Normal");
    } else if (num <= 97) {
      slot11Tag = "C Borderless , Normal";
      slot11Item = findAndConsume("C Borderless , Normal");
    } else if (num <= 100) {
      slot11Tag = "U Borderless , Normal";
      slot11Item = findAndConsume("U Borderless , Normal");
    }
  }

  if (slot10Item && slot11Item && slot10Tag === slot11Tag && slot10Item.value === slot11Item.value) {
    const rangeForTag = ranges.find(range => range.tag === slot11Tag);
    if (rangeForTag) {
      const { minRange, maxRange } = rangeForTag;
      let newValue;
      do {
        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);
        newValue = Math.floor((newNum / 0x100000000) * (maxRange - minRange + 1)) + minRange;
      } while (newValue.toString() === slot10Item.value);

      slot11Item = { value: newValue.toString(), tag: slot11Tag };
    }
  }
  add(slot11Item);

  const slot12 = taggedResults.find(item => item.tag === "Slot12");
  if (slot12) {
    const num = parseInt(slot12.value, 10);
    if (num <= 44) add(findAndConsume("C Frame , Foil"));
    else if (num <= 92) add(findAndConsume("U Frame , Foil"));
    else if (num <= 97) add(findAndConsume("C Borderless , Foil"));
    else if (num <= 100) add(findAndConsume("U Borderless , Foil"));
  }

  const slot13 = taggedResults.find(item => item.tag === "Slot13");
  if (slot13) {
    const num = parseInt(slot13.value, 10);
    if (num <= 86) add(findAndConsume("R , Foil"));
    else add(findAndConsume("M , Foil"));
  }

  const slot14 = taggedResults.find(item => item.tag === "Slot14");
  let slot14Item = null;
  let slot14Tag = null;
  if (slot14) {
    const num = parseInt(slot14.value, 10);
    if (num <= 42) {
      slot14Tag = "R Frame , Normal";
      slot14Item = findAndConsume("R Frame , Normal");
    } else if (num <= 57) {
      slot14Tag = "M Frame , Normal";
      slot14Item = findAndConsume("M Frame , Normal");
    } else if (num <= 87) {
      slot14Tag = "R Borderless , Normal";
      slot14Item = findAndConsume("R Borderless , Normal");
    } else if (num <= 92) {
      slot14Tag = "M Borderless , Normal";
      slot14Item = findAndConsume("M Borderless , Normal");
    } else if (num <= 100) {
      slot14Tag = "Poster , Normal";
      slot14Item = findAndConsume("Poster , Normal");
    }
    add(slot14Item);
  }

  const slot15 = taggedResults.find(item => item.tag === "Slot15");
  let slot15Item = null;
  let slot15Tag = null;
  if (slot15) {
    const num = parseInt(slot15.value, 10);
    if (num <= 42) {
      slot15Tag = "R Frame , Normal";
      slot15Item = findAndConsume("R Frame , Normal");
    } else if (num <= 57) {
      slot15Tag = "M Frame , Normal";
      slot15Item = findAndConsume("M Frame , Normal");
    } else if (num <= 87) {
      slot15Tag = "R Borderless , Normal";
      slot15Item = findAndConsume("R Borderless , Normal");
    } else if (num <= 92) {
      slot15Tag = "M Borderless , Normal";
      slot15Item = findAndConsume("M Borderless , Normal");
    } else if (num <= 100) {
      slot15Tag = "Poster , Normal";
      slot15Item = findAndConsume("Poster , Normal");
    }
  }

  if (slot14Item && slot15Item && slot14Tag === slot15Tag && slot14Item.value === slot15Item.value) {
    const rangeForTag = ranges.find(range => range.tag === slot15Tag);
    if (rangeForTag) {
      const { minRange, maxRange } = rangeForTag;
      let newValue;
      do {
        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);
        newValue = Math.floor((newNum / 0x100000000) * (maxRange - minRange + 1)) + minRange;
      } while (newValue.toString() === slot14Item.value);

      slot15Item = { value: newValue.toString(), tag: slot15Tag };
    }
  }
  add(slot15Item);

  const serialFoil = taggedResults.find(item => item.tag === "Serial , Foil");
  if (serialFoil && serialFoil.value === "5000") {
    serialFoil.value = "1";
    add(serialFoil);
    usedIndexes.add(taggedResults.indexOf(serialFoil));
  } else {
    const slot16 = taggedResults.find(item => item.tag === "Slot16");
    if (slot16) {
      const num = parseInt(slot16.value, 10);
      if (num <= 42) add(findAndConsume("R Frame , Foil"));
      else if (num <= 57) add(findAndConsume("M Frame , Foil"));
      else if (num <= 87) add(findAndConsume("R Borderless , Foil"));
      else if (num <= 92) add(findAndConsume("M Borderless , Foil"));
      else if (num <= 100) add(findAndConsume("Poster , Foil"));
    }
  }

  return filteredResults;
}

async function setData(serverSeed, clientSeed, nonce) {
  const randomNumbers = await generateRandomNumbers(ranges, serverSeed, clientSeed, nonce);
  const taggedResults = await mapTaggedResults(randomNumbers, ranges, serverSeed, clientSeed, nonce);
  const filteredResults = await filterResults(taggedResults, ranges, serverSeed, clientSeed, nonce);

  return {
    success: true,
    result: filteredResults,
    nonce,
    serverSeed,
    clientSeed
  };
}
export { setData, ranges};
