import { DnetServerBuilder } from "../models/DarknetServerOptions";
import {
  commonPasswordDictionary,
  defaultSettingsDictionary,
  dogNameDictionary,
  EUCountries,
  filler,
  letters,
  lettersUppercase,
  numbers,
} from "../models/dictionaryData";
import { DarknetServer } from "../../Server/DarknetServer";
import { ModelIds, MinigamesType } from "../Enums";
import { MAX_PASSWORD_LENGTH } from "../Constants";
import { clampNumber } from "../../utils/helpers/clampNumber";
import { hasFullDarknetAccess } from "../effects/effects";

const getRandomServerConfigBuilder = (difficulty: number) => {
  const tier0Servers = [getNoPasswordConfig];
  const tier1Servers = [getEchoVulnConfig, getDefaultPasswordConfig, getCaptchaConfig];
  const tier2Servers = [getDogNameConfig, getYesn_tConfig, getBufferOverflowConfig];
  const sf15UnlockedServers = hasFullDarknetAccess() ? [getKingOfTheHillConfig, getSpiceLevelConfig] : [];
  const tier3Servers = [
    getSortedEchoVulnConfig,
    getMastermindHintConfig,
    getRomanNumeralConfig,
    getGuessNumberConfig,
    getConvertToBase10Config,
    getDivisibilityTestConfig,
    getPacketSnifferConfig,
    ...sf15UnlockedServers,
  ];
  const tier4Servers = [
    getLargestPrimeFactorConfig,
    getLargeDictionaryConfig,
    getEuCountryDictionaryConfig,
    getTimingAttackConfig,
    getBinaryEncodedConfig,
    getParseArithmeticExpressionConfig,
    getXorMaskEncryptedPasswordConfig,
    getTripleModuloConfig,
  ];

  if (difficulty <= 2) {
    const serverBuilders = [...tier0Servers, ...tier1Servers];
    return serverBuilders[Math.floor(Math.random() * serverBuilders.length)];
  }
  if (difficulty <= 4) {
    const serverBuilders = [...tier0Servers, ...tier1Servers, ...tier1Servers, ...tier2Servers, ...tier3Servers];
    return serverBuilders[Math.floor(Math.random() * serverBuilders.length)];
  }
  if (difficulty <= 8) {
    const serverBuilders = [...tier1Servers, ...tier2Servers, ...tier3Servers];
    return serverBuilders[Math.floor(Math.random() * serverBuilders.length)];
  }
  if (difficulty <= 18) {
    const serverBuilders = [...tier2Servers, ...tier3Servers, ...tier4Servers];
    return serverBuilders[Math.floor(Math.random() * serverBuilders.length)];
  }
  const serverBuilders = [...tier3Servers, ...tier4Servers];
  return serverBuilders[Math.floor(Math.random() * serverBuilders.length)];
};

export const createDarknetServer = (difficulty: number, depth: number, leftOffset: number): DarknetServer => {
  const cappedDifficulty = clampNumber(difficulty, 0, MAX_PASSWORD_LENGTH);
  return serverFactory(getRandomServerConfigBuilder(cappedDifficulty), difficulty, depth, leftOffset);
};

export type ServerConfig = {
  modelId: MinigamesType;
  password: string;
  staticPasswordHint: string;
  passwordHintData?: string;
};

export const serverFactory = (
  serverConfigBuilder: (n: number) => ServerConfig,
  difficulty: number,
  depth: number,
  leftOffset: number,
): DarknetServer => {
  return DnetServerBuilder({
    ...serverConfigBuilder(difficulty),
    difficulty,
    depth,
    leftOffset: leftOffset,
  });
};

export const getEchoVulnConfig = (__difficulty: number): ServerConfig => {
  const hintTemplates = [
    "The password is",
    "The PIN is",
    "Remember to use",
    "It's set to",
    "The key is",
    "The secret is",
  ];
  const password = getPassword(3);
  const hint = `${hintTemplates[Math.floor(Math.random() * hintTemplates.length)]} ${password}`;
  return {
    modelId: ModelIds.EchoVuln,
    password,
    staticPasswordHint: hint,
  };
};

export const getSortedEchoVulnConfig = (difficulty: number): ServerConfig => {
  const hintTemplates = [
    "The password is shuffled",
    "The key is made from",
    "I accidentally sorted the password:",
    "The PIN uses",
  ];
  const password = getPassword(Math.min(2 + difficulty / 7, 9));
  const sortedPassword = password.split("").sort().join("");
  const hint = `${hintTemplates[Math.floor(Math.random() * hintTemplates.length)]} ${sortedPassword}`;
  return {
    modelId: ModelIds.SortedEchoVuln,
    password,
    staticPasswordHint: hint,
    passwordHintData: sortedPassword,
  };
};

export const getDictionaryAttackConfig = (
  __difficulty: number,
  dictionary: readonly string[],
  hintTemplates: string[],
  minigameType: MinigamesType,
): ServerConfig => {
  return {
    modelId: minigameType,
    password: dictionary[Math.floor(Math.random() * dictionary.length)],
    staticPasswordHint: hintTemplates[Math.floor(Math.random() * hintTemplates.length)],
  };
};

export const getNoPasswordConfig = (difficulty: number): ServerConfig => {
  const hintTemplates = [
    "The password is not set",
    "There is no password",
    "The PIN is empty",
    "Did I set a code?",
    "I didn't set a password",
  ];
  return getDictionaryAttackConfig(difficulty, [""], hintTemplates, ModelIds.NoPassword);
};

export const getDefaultPasswordConfig = (difficulty: number): ServerConfig => {
  const hintTemplates = [
    "The password is the default password",
    "It's still the default",
    "The default password is set",
    "I never changed the password",
    "It's still the factory settings",
  ];
  return getDictionaryAttackConfig(difficulty, defaultSettingsDictionary, hintTemplates, ModelIds.DefaultPassword);
};

export const getCaptchaConfig = (difficulty: number): ServerConfig => {
  const password = getPassword(difficulty / 2 + 3);
  const filledPassword = password
    .split("")
    .map((char, i) => {
      if (i >= password.length - 1) {
        return char;
      }
      return char + getFillerChars();
    })
    .join("");

  return {
    modelId: ModelIds.Captcha,
    password,
    staticPasswordHint: "Type the numbers to prove you are human",
    passwordHintData: filledPassword,
  };
};

const getFillerChars = () => {
  let result = "";
  const num = Math.ceil(Math.random() * 3);
  for (let i = 0; i < num; i++) {
    result += filler[Math.floor(Math.random() * filler.length)];
  }
  return result;
};

export const getDogNameConfig = (difficulty: number): ServerConfig => {
  const hintTemplates = ["It's my dog's name", "It's the dog's name", "my first dog's name"];
  return getDictionaryAttackConfig(difficulty, dogNameDictionary, hintTemplates, ModelIds.DogNames);
};

export const getMastermindHintConfig = (difficulty: number): ServerConfig => {
  const alphanumeric = difficulty > 16 && Math.random() < 0.3;
  const passwordLength = Math.min((alphanumeric ? -1 : 2) + difficulty / 5, 10);
  return {
    modelId: ModelIds.MastermindHint,
    password: getPassword(passwordLength, alphanumeric),
    staticPasswordHint: "Only a true master may pass",
  };
};

export const getTimingAttackConfig = (difficulty: number): ServerConfig => {
  const hintTemplates = [
    "I thought about it for some time, but that is not the password.",
    "I spent a while on it, but that's not right",
    "I considered it for a bit, but that's not it",
    "I spent some time on it, but that's not the password",
  ];
  const alphanumeric = difficulty > 16 && Math.random() < 0.3;
  const length = Math.min((alphanumeric ? 0 : 3) + difficulty / 4, 8);
  return {
    modelId: ModelIds.TimingAttack,
    password: getPassword(length, alphanumeric),
    staticPasswordHint: hintTemplates[Math.floor(Math.random() * hintTemplates.length)],
  };
};

export const getRomanNumeralConfig = (difficulty: number): ServerConfig => {
  const password = Math.floor(Math.random() * 10 * (10 * (difficulty + 1)));
  if (difficulty < 8) {
    const encodedPassword = romanNumeralEncoder(password);
    return {
      modelId: ModelIds.RomanNumeral,
      password: `${password}`,
      staticPasswordHint: `The password is the value of the number '${encodedPassword}'`,
      passwordHintData: encodedPassword,
    };
  } else {
    const passwordRangeMin = Math.random() < 0.3 ? 0 : Math.floor(password * (Math.random() * 0.2 + 0.6));
    const passwordRangeMax = password + Math.floor(Math.random() * difficulty * 10 + 10);
    const encodedMin = romanNumeralEncoder(passwordRangeMin);
    const encodedMax = romanNumeralEncoder(passwordRangeMax);
    const hint = `The password is between '${encodedMin}' and '${encodedMax}'`;
    const hintData = `${encodedMin},${encodedMax}`;
    return {
      modelId: ModelIds.RomanNumeral,
      password: `${password}`,
      staticPasswordHint: hint,
      passwordHintData: hintData,
    };
  }
};

export const getLargestPrimeFactorConfig = (difficulty: number): ServerConfig => {
  const largestPrimePasswordDetails = getLargestPrimeFactorPassword(difficulty);
  return {
    modelId: ModelIds.LargestPrimeFactor,
    password: `${largestPrimePasswordDetails.largestPrime}`,
    staticPasswordHint: `The password is the largest prime factor of ${largestPrimePasswordDetails.targetNumber}`,
    passwordHintData: `${largestPrimePasswordDetails.targetNumber}`,
  };
};

export const getGuessNumberConfig = (difficulty: number): ServerConfig => {
  const password = `${Math.floor((Math.random() * 10 * (difficulty + 3)) / 3)}`;
  const maxNumber = 10 ** password.length;
  return {
    modelId: ModelIds.GuessNumber,
    password,
    staticPasswordHint: `The password is a number between 0 and ${maxNumber}`,
  };
};

export const getLargeDictionaryConfig = (difficulty: number): ServerConfig => {
  return getDictionaryAttackConfig(
    difficulty,
    commonPasswordDictionary,
    ["It's a common password"],
    ModelIds.CommonPasswordDictionary,
  );
};

export const getEuCountryDictionaryConfig = (difficulty: number): ServerConfig => {
  return getDictionaryAttackConfig(difficulty, EUCountries, ["My favorite EU country"], ModelIds.EUCountryDictionary);
};

export const getYesn_tConfig = (difficulty: number): ServerConfig => {
  const password = getPassword(3 + difficulty / 2, difficulty > 8);
  return {
    modelId: ModelIds.Yesn_t,
    password,
    staticPasswordHint: "you are one who's'nt authorized",
  };
};

export const getBufferOverflowConfig = (): ServerConfig => {
  const length = Math.floor(4 + Math.random() * 4);
  const password = getPassword(length, true);
  return {
    modelId: ModelIds.BufferOverflow,
    password,
    staticPasswordHint: `Warning: password buffer is ${length} bytes`,
  };
};

export const getBinaryEncodedConfig = (difficulty: number): ServerConfig => {
  const password = getPassword(2 + difficulty / 5, difficulty > 8);
  const binaryEncodedPassword = password
    .split("")
    .map((char) => char.charCodeAt(0).toString(2).padStart(8, "0"))
    .join(" ");
  return {
    modelId: ModelIds.BinaryEncodedFeedback,
    password,
    staticPasswordHint: "beep boop",
    passwordHintData: binaryEncodedPassword,
  };
};

export const getXorMaskEncryptedPasswordConfig = (): ServerConfig => {
  const password = getPassword(3 + Math.random() * 3, true);
  let passwordWithXorMaskApplied: string;
  let xorMaskStrings: string[];

  do {
    passwordWithXorMaskApplied = "";
    xorMaskStrings = [];
    for (const c of password) {
      const charCode = c.charCodeAt(0);
      const xorMask = Math.floor(Math.random() * 32);
      xorMaskStrings.push(xorMask.toString(2).padStart(8, "0"));
      passwordWithXorMaskApplied += String.fromCharCode(charCode ^ xorMask);
    }
    // Prevent characters that would break parsing in encoded output
  } while (passwordWithXorMaskApplied.includes(";") || passwordWithXorMaskApplied.includes(" "));

  return {
    modelId: ModelIds.encryptedPassword,
    password,
    staticPasswordHint: `XOR mask encrypted password: "${passwordWithXorMaskApplied}".`,
    passwordHintData: `${passwordWithXorMaskApplied};${xorMaskStrings.join(" ")}`,
  };
};

export const getSpiceLevelConfig = (difficulty: number): ServerConfig => {
  const password = getPassword(3 + difficulty / 3, difficulty > 8);
  return {
    modelId: ModelIds.SpiceLevel,
    password,
    staticPasswordHint: "!!🌶️!!",
  };
};

export const getConvertToBase10Config = (difficulty: number): ServerConfig => {
  const password = Math.ceil(Math.random() * 99 * (difficulty + 1));
  const bases = [2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16];
  let base = bases[Math.floor(Math.random() * bases.length)];
  if (difficulty > 12) {
    base += bases[Math.floor(Math.random() * bases.length)] / 10;
  }
  const encodedPassword = encodeNumberInBaseN(password, base);
  return {
    modelId: ModelIds.ConvertToBase10,
    password: `${password}`,
    staticPasswordHint: `the password is the base ${base} number ${encodedPassword} in base 10`,
    passwordHintData: `${base},${encodedPassword}`,
  };
};

export const getParseArithmeticExpressionConfig = (difficulty: number): ServerConfig => {
  let expression = generateSimpleArithmeticExpression(difficulty);
  const result = parseSimpleArithmeticExpression(expression);
  if (difficulty > 12) {
    expression = expression.replaceAll("*", "ҳ").replaceAll("/", "÷").replaceAll("+", "➕").replaceAll("-", "➖");
  }
  if ((difficulty > 16 && Math.random() < 0.3) || Math.random() < 0.01) {
    expression += getCodeInjection();
  }
  const parenCount = expression.split("(").length - 1;
  if (difficulty > 20 && Math.random() < 0.3 && parenCount > 1) {
    expression = expression.replace("(", "(ns.exit(),");
  }
  return {
    modelId: ModelIds.parsedExpression,
    password: `${result}`,
    staticPasswordHint: `The password is the evaluation of this expression`,
    passwordHintData: expression,
  };
};

export const getDivisibilityTestConfig = (difficulty: number): ServerConfig => {
  const password = getPasswordMadeUpOfPrimesProduct(difficulty);
  return {
    modelId: ModelIds.divisibilityTest,
    password: `${password}`,
    staticPasswordHint: `The password is divisible by 1 ;)`,
  };
};

export const getTripleModuloConfig = (difficulty: number): ServerConfig => {
  const password = getPassword(3 + difficulty / 5);
  return {
    modelId: ModelIds.tripleModulo,
    password: `${password}`,
    staticPasswordHint: `(password % n) % (n % 32)`,
  };
};

export const getKingOfTheHillConfig = (difficulty: number): ServerConfig => {
  const password = getPassword(Math.min(1 + difficulty / 6, 10));
  return {
    modelId: ModelIds.globalMaxima,
    password,
    staticPasswordHint: "Ascend the highest mountain!",
  };
};

export const getPacketSnifferConfig = (difficulty: number): ServerConfig => {
  return {
    modelId: ModelIds.packetSniffer,
    password: getPassword(3 + Math.random() * 6, difficulty > 8),
    staticPasswordHint: "(I'm busy browsing social media at the cafe)",
  };
};

export const encodeNumberInBaseN = (decimalNumber: number, base: number) => {
  const characters = [...numbers.split(""), ...lettersUppercase.split("")];
  let digits = Math.floor(Math.log(decimalNumber) / Math.log(base));
  let remaining = decimalNumber;
  let result: string = "";

  while (remaining >= 0.0001 || digits >= 0) {
    if (digits === -1) {
      result += ".";
    }
    const place = Math.floor(remaining / base ** digits);
    result += characters[place];
    remaining -= place * base ** digits;
    digits -= 1;
  }

  return result;
};

export const parseBaseNNumberString = (numberString: string, base: number): number => {
  const characters = [...numbers.split(""), ...lettersUppercase.split("")];
  let result = 0;
  let index = 0;
  let digit = numberString.split(".")[0].length - 1;

  while (index < numberString.length) {
    const currentDigit = numberString[index];
    if (currentDigit === ".") {
      index += 1;
      continue;
    }
    result += characters.indexOf(currentDigit) * base ** digit;
    index += 1;
    digit -= 1;
  }

  return result;
};

// example:  4 + 5 * ( 6 + 7 ) / 2
export const parseSimpleArithmeticExpression = (expression: string): number => {
  const tokens = cleanArithmeticExpression(expression).split("");

  // Identify parentheses
  let currentDepth = 0;
  const depth = tokens.map((token) => {
    if (token === "(") {
      currentDepth += 1;
    } else if (token === ")") {
      currentDepth -= 1;
      return currentDepth + 1;
    }
    return currentDepth;
  });
  const depth1Start = depth.indexOf(1);
  // find the last 1 before the first 0 after depth1Start
  const firstZeroAfterDepth1Start = depth.indexOf(0, depth1Start);
  const depth1End = firstZeroAfterDepth1Start === -1 ? depth.length - 1 : firstZeroAfterDepth1Start - 1;
  if (depth1Start !== -1) {
    const subExpression = tokens.slice(depth1Start + 1, depth1End).join("");
    const result = parseSimpleArithmeticExpression(subExpression);
    tokens.splice(depth1Start, depth1End - depth1Start + 1, result.toString());
    return parseSimpleArithmeticExpression(tokens.join(""));
  }

  // handle multiplication and division
  let remainingExpression = tokens.join("");

  // breakdown and explanation for this regex: https://regex101.com/r/mZhiBn/1
  const multiplicationDivisionRegex = /(-?\d*\.?\d+) *([*/]) *(-?\d*\.?\d+)/;
  let match = remainingExpression.match(multiplicationDivisionRegex);

  while (match) {
    const [__, left, operator, right] = match;
    const result = operator === "*" ? parseFloat(left) * parseFloat(right) : parseFloat(left) / parseFloat(right);
    const resultString = Math.abs(result) < 0.000001 ? result.toFixed(20) : result.toString();
    remainingExpression = remainingExpression.replace(match[0], resultString);
    match = remainingExpression.match(multiplicationDivisionRegex);
  }

  // handle addition and subtraction
  const additionSubtractionRegex = /(-?\d*\.?\d+) *([+-]) *(-?\d*\.?\d+)/;
  match = remainingExpression.match(additionSubtractionRegex);

  while (match) {
    const [__, left, operator, right] = match;
    const result = operator === "+" ? parseFloat(left) + parseFloat(right) : parseFloat(left) - parseFloat(right);
    remainingExpression = remainingExpression.replace(match[0], result.toString());
    match = remainingExpression.match(additionSubtractionRegex);
  }

  const [__, leftover] = remainingExpression.match(/(-?\d*\.?\d+)/) ?? ["", ""];

  return parseFloat(leftover);
};

export const generateSimpleArithmeticExpression = (difficulty: number): string => {
  const operators = ["+", "-", "*", "/"];
  const operatorCount = Math.floor(difficulty / 4);
  const expression = [];
  for (let i = 0; i < operatorCount; i++) {
    expression.push(Math.ceil(Math.random() * 98));
    expression.push(operators[Math.floor(Math.random() * operators.length)]);

    if (difficulty > 5 && Math.random() < difficulty / (difficulty + 50)) {
      expression.push("(");
      expression.push(generateSimpleArithmeticExpression(difficulty / 2));
      expression.push(")");
      expression.push(operators[Math.floor(Math.random() * operators.length)]);
    }
  }
  expression.push(Math.ceil(Math.random() * 98));

  const result = expression.join(" ");

  try {
    const calc = parseSimpleArithmeticExpression(cleanArithmeticExpression(result));
    if (Math.abs(calc) < 0.1) {
      return generateSimpleArithmeticExpression(difficulty);
    }
  } catch (__) {
    return generateSimpleArithmeticExpression(difficulty);
  }

  if (difficulty > 18) {
    return result.replaceAll("*", "ҳ").replaceAll("/", "÷").replaceAll("+", "➕").replaceAll("-", "➖");
  }

  return result;
};

export const cleanArithmeticExpression = (expression: string): string => {
  const expressionWithFixedSymbols = expression
    .replaceAll("ҳ", "*")
    .replaceAll("÷", "/")
    .replaceAll("➕", "+")
    .replaceAll("➖", "-")
    .replaceAll("ns.exit(),", "");
  return expressionWithFixedSymbols.split(",")[0];
};

const getCodeInjection = () => {
  return ` , !globalThis.pwn3d && (globalThis.pwn3d=true, alert("You've been hacked! You evaluated a string and let me inject code, didn't you? HAHAHAHA!") , globalThis.openDevMenu() ) , ns.exit()`;
};

export const getPassword = (length: number, allowLetters = false): string => {
  const characters = numbers + (allowLetters ? letters : "");
  let password = "";
  const cappedLength = clampNumber(length, 1, MAX_PASSWORD_LENGTH);
  for (let i = 0; i < cappedLength; i++) {
    password += characters[Math.floor(Math.random() * characters.length)];
  }
  if (!allowLetters && Number(password) > Number.MAX_SAFE_INTEGER) {
    password = password.slice(0, 15);
  }
  // prevent leading zeros in multi-digit numeric passwords
  if (!allowLetters) {
    return Number(password).toString();
  }
  return password;
};

export const getPasswordType = (password: string): "numeric" | "alphabetic" | "alphanumeric" | "ASCII" | "unicode" => {
  const passwordArr = password.split("");

  if (passwordArr.every((char) => numbers.includes(char))) {
    return "numeric";
  }
  if (passwordArr.every((char) => letters.includes(char))) {
    return "alphabetic";
  }
  if (passwordArr.every((char) => numbers.includes(char) || letters.includes(char))) {
    return "alphanumeric";
  }
  if (passwordArr.every((char) => char.charCodeAt(0) < 128)) {
    return "ASCII";
  }
  return "unicode";
};

export const romanNumeralEncoder = (input: number): string => {
  const romanNumerals: { [key: number]: string } = {
    1: "I",
    4: "IV",
    5: "V",
    9: "IX",
    10: "X",
    40: "XL",
    50: "L",
    90: "XC",
    100: "C",
    400: "CD",
    500: "D",
    900: "CM",
    1000: "M",
  };

  const keys = Object.keys(romanNumerals).map((key) => Number(key));
  let result = "";
  for (let i = keys.length - 1; i >= 0; i--) {
    const key = keys[i];
    while (input >= key) {
      result += romanNumerals[key];
      input -= key;
    }
  }
  return result || "nulla";
};

export const romanNumeralDecoder = (input: string): number => {
  if (input.toLowerCase() === "nulla") {
    return 0;
  }

  const romanToInt: { [key: string]: number } = {
    I: 1,
    V: 5,
    X: 10,
    L: 50,
    C: 100,
    D: 500,
    M: 1000,
  };
  let total = 0;
  let prevValue = 0;

  for (let i = input.length - 1; i >= 0; i--) {
    const currentValue = romanToInt[input[i]];
    if (currentValue < prevValue) {
      total -= currentValue;
    } else {
      total += currentValue;
    }
    prevValue = currentValue;
  }

  return total;
};

export const smallPrimes = [
  2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
];
export const largePrimes = [
  1069, 1409, 1471, 1567, 1597, 1601, 1697, 1747, 1801, 1889, 1979, 1999, 2063, 2207, 2371, 2503, 2539, 2693, 2741,
  2753, 2801, 2819, 2837, 2909, 2939, 3169, 3389, 3571, 3761, 3881, 4217, 4289, 4547, 4729, 4789, 4877, 4943, 4951,
  4957, 5393, 5417, 5419, 5441, 5519, 5527, 5647, 5779, 5881, 6007, 6089, 6133, 6389, 6451, 6469, 6547, 6661, 6719,
  6841, 7103, 7549, 7559, 7573, 7691, 7753, 7867, 8053, 8081, 8221, 8329, 8599, 8677, 8761, 8839, 8963, 9103, 9199,
  9343, 9467, 9551, 9601, 9739, 9749, 9859,
];

const getLargestPrimeFactorPassword = (difficulty = 1) => {
  const factorCount = 1 + Math.min(5, Math.floor(difficulty / 3));

  const largePrimeIndex = 2 + Math.floor(Math.random() * (largePrimes.length - 2));
  const largestPrime = largePrimes[largePrimeIndex];
  let number = largestPrime;
  for (let i = 1; i <= factorCount; i++) {
    number *= smallPrimes[Math.floor(Math.random() * smallPrimes.length)];
  }

  return {
    largestPrime: largestPrime,
    targetNumber: number,
  };
};

const getPasswordMadeUpOfPrimesProduct = (difficulty = 1) => {
  const scale = Math.min(difficulty / 2, 15);
  let password;

  do {
    password = BigInt(Math.floor(Math.random() * 5 * (scale + 1)) + 1);
    for (let i = 0; i < scale / 3; i++) {
      if (Math.random() < 0.5) {
        password *= BigInt(Math.ceil(Math.random() * 5));
      } else {
        password *= BigInt(smallPrimes[Math.floor(Math.random() * smallPrimes.length)]);
      }
    }
    if (difficulty > 12) {
      password *= BigInt(largePrimes[Math.floor(Math.random() * largePrimes.length)]);
    }
    if (difficulty > 24) {
      password *= BigInt(largePrimes[Math.floor(Math.random() * largePrimes.length)]);
    }
  } while (BigInt(Number(password)) !== password); // ensure it fits in JS number precision
  return password.toString();
};
