import { Config } from "../config/store";
import { setConfig, setQuoteLengthAll, toggleFunbox } from "../config/setters";
import * as CustomText from "./custom-text";
import { Wordset, FunboxWordsFrequency, withWords } from "./wordset";
import QuotesController, {
  Quote,
  QuoteWithTextSplit,
} from "../controllers/quotes-controller";
import * as TestWords from "./test-words";
import * as BritishEnglish from "./british-english";
import * as LazyMode from "./lazy-mode";
import * as EnglishPunctuation from "./english-punctuation";
import * as PractiseWords from "./practise-words";
import * as Misc from "../utils/misc";
import * as Strings from "../utils/strings";
import * as Arrays from "../utils/arrays";
import * as TestState from "../test/test-state";
import * as GetText from "../utils/generate";
import { FunboxWordOrder } from "../utils/json-data";
import {
  findSingleActiveFunboxWithFunction,
  getActiveFunboxes,
  getActiveFunboxesWithFunction,
  isFunboxActiveWithFunction,
} from "./funbox/list";
import { WordGenError } from "../utils/word-gen-error";

import { showLoaderBar, hideLoaderBar } from "../states/loader-bar";
import { PolyglotWordset } from "./funbox/funbox-functions";
import { LanguageObject } from "@monkeytype/schemas/languages";

//pin implementation
const random = Math.random;

function shouldCapitalize(lastChar: string): boolean {
  return /[?!.؟]/.test(lastChar);
}

let spanishSentenceTracker = "";
export async function punctuateWord(
  previousWord: string,
  currentWord: string,
  index: number,
  maxindex: number,
): Promise<string> {
  let word = currentWord;

  const currentLanguage = Config.language.split("_")[0];

  const lastChar = Strings.getLastChar(previousWord);

  const funbox = findSingleActiveFunboxWithFunction("punctuateWord");
  if (funbox) {
    return funbox.functions.punctuateWord(word);
  }
  if (
    currentLanguage !== "code" &&
    currentLanguage !== "georgian" &&
    (index === 0 || shouldCapitalize(lastChar))
  ) {
    //always capitalise the first word or if there was a dot unless using a code alphabet or the Georgian language

    word = Strings.capitalizeFirstLetterOfEachWord(word);

    if (currentLanguage === "turkish") {
      word = word.replace(/I/g, "İ");
    }

    if (currentLanguage === "spanish") {
      const rand = random();
      if (rand > 0.9) {
        word = "¿" + word;
        spanishSentenceTracker = "?";
      } else if (rand > 0.8) {
        word = "¡" + word;
        spanishSentenceTracker = "!";
      }
    }
  } else if (
    (random() < 0.1 &&
      lastChar !== "." &&
      lastChar !== "," &&
      index !== maxindex - 2) ||
    index === maxindex - 1
  ) {
    if (currentLanguage === "spanish") {
      if (spanishSentenceTracker === "?" || spanishSentenceTracker === "!") {
        word += spanishSentenceTracker;
        spanishSentenceTracker = "";
      }
    } else {
      const rand = random();
      if (rand <= 0.8) {
        if (currentLanguage === "kurdish") {
          word += ".";
        } else if (
          currentLanguage === "nepali" ||
          currentLanguage === "bangla" ||
          currentLanguage === "hindi"
        ) {
          word += "।";
        } else if (
          currentLanguage === "japanese" ||
          currentLanguage === "chinese"
        ) {
          word += "。";
        } else {
          word += ".";
        }
      } else if (rand > 0.8 && rand < 0.9) {
        if (currentLanguage === "french") {
          word = "?";
        } else if (
          currentLanguage === "arabic" ||
          currentLanguage === "persian" ||
          currentLanguage === "urdu" ||
          currentLanguage === "kurdish"
        ) {
          word += "؟";
        } else if (currentLanguage === "greek") {
          word += ";";
        } else if (
          currentLanguage === "japanese" ||
          currentLanguage === "chinese"
        ) {
          word += "？";
        } else {
          word += "?";
        }
      } else {
        if (currentLanguage === "french") {
          word = "!";
        } else if (
          currentLanguage === "japanese" ||
          currentLanguage === "chinese"
        ) {
          word += "！";
        } else {
          word += "!";
        }
      }
    }
  } else if (
    random() < 0.01 &&
    lastChar !== "," &&
    lastChar !== "." &&
    currentLanguage !== "russian"
  ) {
    word = `"${word}"`;
  } else if (
    random() < 0.011 &&
    lastChar !== "," &&
    lastChar !== "." &&
    currentLanguage !== "russian" &&
    currentLanguage !== "ukrainian" &&
    currentLanguage !== "slovak"
  ) {
    word = `'${word}'`;
  } else if (random() < 0.012 && lastChar !== "," && lastChar !== ".") {
    if (currentLanguage === "code") {
      const r = random();
      const brackets = ["()", "{}", "[]", "<>"];

      // add `word` in javascript
      if (Config.language.startsWith("code_javascript")) {
        brackets.push("``");
      }

      const index = Math.floor(r * brackets.length);
      const bracket = brackets[index] as string;

      word = `${bracket[0]}${word}${bracket[1]}`;
    } else if (
      currentLanguage === "japanese" ||
      currentLanguage === "chinese"
    ) {
      word = `（${word}）`;
    } else {
      word = `(${word})`;
    }
  } else if (
    random() < 0.013 &&
    lastChar !== "," &&
    lastChar !== "." &&
    lastChar !== ";" &&
    lastChar !== "؛" &&
    lastChar !== ":" &&
    lastChar !== "；" &&
    lastChar !== "："
  ) {
    if (currentLanguage === "french") {
      word = ":";
    } else if (currentLanguage === "chinese") {
      word += "：";
    } else {
      word += ":";
    }
  } else if (
    random() < 0.014 &&
    lastChar !== "," &&
    lastChar !== "." &&
    previousWord !== "-"
  ) {
    word = "-";
  } else if (
    random() < 0.015 &&
    lastChar !== "," &&
    lastChar !== "." &&
    lastChar !== ";" &&
    lastChar !== "؛" &&
    lastChar !== "；" &&
    lastChar !== "："
  ) {
    if (currentLanguage === "french") {
      word = ";";
    } else if (currentLanguage === "greek") {
      // Normally U+00B7 ('middle dot' or 'ano teleia') would be used here.
      // However, a) it has fallen into disuse in contemporary times and
      // b) there isn't a dedicated key on a keyboard to input it
      word = ".";
    } else if (currentLanguage === "arabic" || currentLanguage === "kurdish") {
      word += "؛";
    } else if (currentLanguage === "chinese") {
      word += "；";
    } else {
      word += ";";
    }
  } else if (random() < 0.2 && lastChar !== ",") {
    if (
      currentLanguage === "arabic" ||
      currentLanguage === "urdu" ||
      currentLanguage === "persian" ||
      currentLanguage === "kurdish"
    ) {
      word += "،";
    } else if (currentLanguage === "japanese") {
      word += "、";
    } else if (currentLanguage === "chinese") {
      word += "，";
    } else {
      word += ",";
    }
  } else if (random() < 0.25 && currentLanguage === "code") {
    const specials = ["{", "}", "[", "]", "(", ")", ";", "=", "+", "%", "/"];
    const specialsC = [
      "{",
      "}",
      "[",
      "]",
      "(",
      ")",
      ";",
      "=",
      "+",
      "%",
      "/",
      "/*",
      "*/",
      "//",
      "!=",
      "==",
      "<=",
      ">=",
      "||",
      "&&",
      "<<",
      ">>",
      "%=",
      "&=",
      "*=",
      "++",
      "+=",
      "--",
      "-=",
      "/=",
      "^=",
      "|=",
    ];

    if (
      (Config.language.startsWith("code_c") &&
        !Config.language.startsWith("code_css")) ||
      Config.language.startsWith("code_arduino")
    ) {
      word = Arrays.randomElementFromArray(specialsC);
    } else {
      if (Config.language.startsWith("code_javascript")) {
        word = Arrays.randomElementFromArray([...specials, "`"]);
      } else {
        word = Arrays.randomElementFromArray(specials);
      }
    }
  } else if (
    random() < 0.5 &&
    currentLanguage === "english" &&
    (await EnglishPunctuation.check(word))
  ) {
    word = await applyEnglishPunctuationToWord(word);
  }

  if (word.includes("\t")) {
    word = word.replace(/\t/g, "");
    word += "\t";
  }
  if (word.includes("\n")) {
    word = word.replace(/\n/g, "");
    word += "\n";
  }

  return word;
}

async function applyEnglishPunctuationToWord(word: string): Promise<string> {
  return EnglishPunctuation.replace(word);
}

function getFunboxWordsFrequency(): FunboxWordsFrequency | undefined {
  const funbox = findSingleActiveFunboxWithFunction("getWordsFrequencyMode");
  if (funbox) {
    return funbox.functions.getWordsFrequencyMode();
  }
  return undefined;
}

async function getFunboxSection(): Promise<string[]> {
  const ret = [];

  const funbox = findSingleActiveFunboxWithFunction("pullSection");

  if (funbox) {
    const section = await funbox.functions.pullSection(Config.language);

    if (section === false || section === undefined) {
      toggleFunbox(funbox.name);
      throw new Error("Failed to pull section");
    }

    for (const word of section.words) {
      if (ret.length >= Config.words && Config.mode === "words") {
        break;
      }
      ret.push(word);
    }
  }
  return ret;
}

function getFunboxWord(
  word: string,
  wordIndex: number,
  wordset?: Wordset,
): string {
  const funbox = findSingleActiveFunboxWithFunction("getWord");

  if (funbox) {
    word = funbox.functions.getWord(wordset, wordIndex);
  }
  return word;
}

function applyFunboxesToWord(
  word: string,
  wordIndex: number,
  wordsBound: number,
): string {
  for (const fb of getActiveFunboxesWithFunction("alterText")) {
    word = fb.functions.alterText(word, wordIndex, wordsBound);
  }
  return word;
}

async function applyBritishEnglishToWord(
  word: string,
  previousWord: string,
): Promise<string> {
  if (!Config.britishEnglish) return word;
  if (!Config.language.includes("english")) return word;
  if (
    Config.mode === "quote" &&
    TestWords.currentQuote?.britishText !== undefined &&
    TestWords.currentQuote?.britishText !== ""
  ) {
    return word;
  }

  return await BritishEnglish.replace(word, previousWord);
}

function applyLazyModeToWord(word: string, language: LanguageObject): string {
  // polyglot mode, use the word's actual language
  if (currentWordset && currentWordset instanceof PolyglotWordset) {
    const langName = currentWordset.wordsWithLanguage.get(word);
    const langProps = langName
      ? currentWordset.languageProperties.get(langName)
      : undefined;
    const allowLazyMode =
      (langProps && !langProps.noLazyMode) === true || Config.mode === "custom";
    if (Config.lazyMode && allowLazyMode && langProps) {
      word = LazyMode.replaceAccents(word, langProps.additionalAccents);
    }
    return word;
  }

  // normal mode
  const allowLazyMode = !language.noLazyMode || Config.mode === "custom";
  if (Config.lazyMode && allowLazyMode) {
    word = LazyMode.replaceAccents(word, language.additionalAccents);
  }
  return word;
}

export function getWordOrder(): FunboxWordOrder {
  const wordOrderProperty = getActiveFunboxes()
    .flatMap((fb) => fb.properties ?? [])
    .find((prop) => prop.startsWith("wordOrder:"));

  return (wordOrderProperty?.split(":")[1] as FunboxWordOrder) ?? "normal";
}

export function getLimit(): number {
  if (Config.mode === "zen") {
    return 0;
  }

  let limit = 100;

  const currentQuote = TestWords.currentQuote;

  if (Config.mode === "quote" && currentQuote === null) {
    throw new WordGenError("Random quote is null");
  }

  const funboxToPush =
    getActiveFunboxes()
      .flatMap((fb) => fb.properties ?? [])
      .find((prop) => prop.startsWith("toPush:")) ?? "";

  if (Config.showAllLines) {
    if (Config.mode === "custom") {
      limit = CustomText.getLimitValue();
    }
    if (Config.mode === "words") {
      limit = Config.words;
    }
    if (Config.mode === "quote") {
      limit = (currentQuote as QuoteWithTextSplit).textSplit.length;
    }
  }

  //infinite words
  if (Config.mode === "words" && Config.words === 0) {
    limit = 100;
  }

  //custom
  if (Config.mode === "custom") {
    if (
      CustomText.getLimitValue() === 0 ||
      CustomText.getLimitMode() === "time"
    ) {
      limit = 100;
    } else {
      limit =
        CustomText.getLimitValue() > 100 ? 100 : CustomText.getLimitValue();
    }
  }

  //funboxes
  if (funboxToPush) {
    limit = +(funboxToPush.split(":")[1] as string);
  }

  //make sure the limit is not higher than the word count
  if (Config.mode === "words" && Config.words !== 0 && Config.words < limit) {
    limit = Config.words;
  }

  if (
    Config.mode === "quote" &&
    (currentQuote as QuoteWithTextSplit).textSplit.length < limit
  ) {
    limit = (currentQuote as QuoteWithTextSplit).textSplit.length;
  }

  if (
    Config.mode === "custom" &&
    CustomText.getLimitMode() === "word" &&
    CustomText.getLimitValue() < limit &&
    CustomText.getLimitValue() !== 0
  ) {
    limit = CustomText.getLimitValue();
  }

  return limit;
}

async function getQuoteWordList(
  language: LanguageObject,
  wordOrder?: FunboxWordOrder,
): Promise<string[]> {
  if (TestState.isRepeated) {
    if (currentWordset === null) {
      throw new WordGenError("Current wordset is null");
    }

    TestWords.setCurrentQuote(previousRandomQuote);

    // need to re-reverse the words if the test is repeated
    // because it will be reversed again in the generateWords function
    if (wordOrder === "reverse") {
      return currentWordset.words.reverse();
    } else {
      return currentWordset.words;
    }
  }
  const languageToGet = language.name.startsWith("swiss_german")
    ? "german"
    : language.name;

  showLoaderBar();
  const quotesCollection = await QuotesController.getQuotes(
    languageToGet,
    Config.quoteLength,
  );
  hideLoaderBar();

  if (quotesCollection.length === 0) {
    setConfig("mode", "words");
    throw new WordGenError(
      `No ${Config.language
        .replace(/_\d*k$/g, "")
        .replace(/_/g, " ")} quotes found`,
    );
  }

  let rq: Quote;
  if (Config.quoteLength.includes(-2) && Config.quoteLength.length === 1) {
    const targetQuote = QuotesController.getQuoteById(
      TestState.selectedQuoteId,
    );
    if (targetQuote === undefined) {
      setQuoteLengthAll();
      throw new WordGenError(
        `Quote ${TestState.selectedQuoteId} does not exist`,
      );
    }
    rq = targetQuote;
  } else if (Config.quoteLength.includes(-3)) {
    const randomQuote = QuotesController.getRandomFavoriteQuote(
      Config.language,
    );
    if (randomQuote === null) {
      setQuoteLengthAll();
      throw new WordGenError("No favorite quotes found");
    }
    rq = randomQuote;
  } else {
    const randomQuote = QuotesController.getRandomQuote();
    if (randomQuote === null) {
      setQuoteLengthAll();
      throw new WordGenError("No quotes found for selected quote length");
    }
    rq = randomQuote;
  }

  rq.language = Strings.removeLanguageSize(Config.language);
  rq.text = rq.text.replace(/ +/gm, " ");
  rq.text = rq.text.replace(/( *(\r\n|\r|\n) *)/g, "\n ");
  rq.text = rq.text.replace(/…/g, "...");
  rq.text = rq.text.trim();

  if (
    rq.britishText !== undefined &&
    rq.britishText !== "" &&
    Config.britishEnglish
  ) {
    rq.textSplit = rq.britishText.split(" ");
  } else {
    rq.textSplit = rq.text.split(" ");
  }

  TestWords.setCurrentQuote(rq as QuoteWithTextSplit);

  if (TestWords.currentQuote === null) {
    throw new WordGenError("Random quote is null");
  }

  if (TestWords.currentQuote.textSplit === undefined) {
    throw new WordGenError("Random quote textSplit is undefined");
  }

  return TestWords.currentQuote.textSplit;
}

let currentWordset: Wordset | null = null;
let currentLanguage: LanguageObject | null = null;
let isCurrentlyUsingFunboxSection = false;

type GenerateWordsReturn = {
  words: string[];
  sectionIndexes: number[];
  hasTab: boolean;
  hasNewline: boolean;
  allRightToLeft?: boolean;
  allLigatures?: boolean;
};

let previousRandomQuote: QuoteWithTextSplit | null = null;

export async function generateWords(
  language: LanguageObject,
): Promise<GenerateWordsReturn> {
  if (!TestState.isRepeated) {
    previousGetNextWordReturns = [];
  }
  previousRandomQuote = TestWords.currentQuote;
  TestWords.setCurrentQuote(null);
  currentSection = [];
  sectionIndex = 0;
  sectionHistory = [];
  currentLanguage = language;
  const ret: GenerateWordsReturn = {
    words: [],
    sectionIndexes: [],
    hasTab: false,
    hasNewline: false,
    allRightToLeft: language.rightToLeft,
    allLigatures: language.ligatures ?? false,
  };

  isCurrentlyUsingFunboxSection = isFunboxActiveWithFunction("pullSection");

  const wordOrder = getWordOrder();
  console.debug("Word order", wordOrder);

  let wordList = language.words;
  if (Config.mode === "custom") {
    wordList = CustomText.getText();
  } else if (Config.mode === "quote") {
    wordList = await getQuoteWordList(language, wordOrder);
  } else if (Config.mode === "zen") {
    wordList = [];
  }

  const customAndUsingPipeDelimiter =
    Config.mode === "custom" && CustomText.getPipeDelimiter();

  const limit = getLimit();
  console.debug(
    `${customAndUsingPipeDelimiter ? "Section" : "Word"} limit ${limit}`,
  );

  if (wordOrder === "reverse") {
    wordList = wordList.reverse();
  }

  const funbox = findSingleActiveFunboxWithFunction("withWords");
  if (funbox) {
    const result = await funbox.functions.withWords(wordList);
    // PolyglotWordset if polyglot otherwise Wordset
    if (result instanceof PolyglotWordset) {
      const polyglotResult = result;
      currentWordset = polyglotResult;
      // set allLigatures if any language in languageProperties has ligatures true
      ret.allLigatures = Array.from(
        polyglotResult.languageProperties.values(),
      ).some((props) => !!props.ligatures);
    } else {
      currentWordset = result;
    }
  } else {
    currentWordset = await withWords(wordList);
  }

  console.debug("Wordset", currentWordset);

  if (limit === 0) {
    return ret;
  }

  let stop = false;
  let i = 0;
  while (!stop) {
    const nextWord = await getNextWord(
      i,
      limit,
      Arrays.nthElementFromArray(ret.words, -1) ?? "",
      Arrays.nthElementFromArray(ret.words, -2) ?? "",
    );
    ret.words.push(nextWord.word);
    ret.sectionIndexes.push(nextWord.sectionIndex);

    if (customAndUsingPipeDelimiter) {
      //generate a given number of sections, make sure to not cut a section off
      const sectionFinishedAndOverLimit =
        currentSection.length === 0 && sectionIndex >= limit;
      //make sure we dont go over a hard limit, in cases where the sections are very large
      const upperWordLimit = ret.words.length >= 100;
      if (sectionFinishedAndOverLimit || upperWordLimit) {
        stop = true;
      }
    } else if (ret.words.length >= limit) {
      stop = true;
    }
    i++;
  }

  const quote = TestWords.currentQuote;

  if (Config.mode === "quote" && quote === null) {
    throw new WordGenError("Random quote is null");
  }

  ret.hasTab =
    ret.words.some((w) => w.includes("\t")) ||
    currentWordset.words.some((w) => w.includes("\t")) ||
    (Config.mode === "quote" &&
      (quote as QuoteWithTextSplit).textSplit.some((w) => w.includes("\t")));
  ret.hasNewline =
    ret.words.some((w) => w.includes("\n")) ||
    currentWordset.words.some((w) => w.includes("\n")) ||
    (Config.mode === "quote" &&
      (quote as QuoteWithTextSplit).textSplit.some((w) => w.includes("\n")));

  sectionHistory = []; //free up a bit of memory? is that even a thing?
  return ret;
}

export let sectionIndex = 0;
export let currentSection: string[] = [];
let sectionHistory: string[] = [];

let previousGetNextWordReturns: GetNextWordReturn[] = [];

type GetNextWordReturn = {
  word: string;
  sectionIndex: number;
};

//generate next word
export async function getNextWord(
  wordIndex: number,
  wordsBound: number,
  previousWord: string,
  previousWord2: string | undefined,
): Promise<GetNextWordReturn> {
  console.debug("Getting next word", {
    isRepeated: TestState.isRepeated,
    currentWordset,
    wordIndex,
    language: currentLanguage,
    wordsBound,
    previousWord,
    previousWord2,
  });

  if (currentWordset === null) {
    throw new WordGenError("Current wordset is null");
  }

  if (currentLanguage === null) {
    throw new WordGenError("Current language is null");
  }

  //because quote test can be repeated in the middle of a test
  //we cant rely on data inside previousGetNextWordReturns
  //because it might not include the full quote
  if (TestState.isRepeated && Config.mode !== "quote") {
    const repeated = previousGetNextWordReturns[wordIndex];

    if (repeated === undefined) {
      // if the repeated word is undefined, that means we are out of words from the previous test
      // we need to either throw, or revert to random generation
      // reverting should only happen in certain cases

      let continueRandomGeneration = false;

      if (
        Config.mode === "time" ||
        (Config.mode === "custom" && CustomText.getLimitMode() === "time") ||
        (Config.mode === "custom" &&
          CustomText.getLimitMode() === "word" &&
          wordIndex < CustomText.getLimitValue()) ||
        (Config.mode === "custom" &&
          CustomText.getLimitMode() === "section" &&
          sectionIndex < CustomText.getLimitValue()) ||
        (Config.mode === "words" && wordIndex < Config.words)
      ) {
        continueRandomGeneration = true;
      }

      if (!continueRandomGeneration) {
        throw new WordGenError("Repeated word is undefined");
      } else {
        console.debug(
          "Repeated word is undefined but random generation is allowed - getting random word",
        );
      }
    } else {
      console.debug("Repeated word: ", repeated);
      sectionIndex++;
      return repeated;
    }
  }

  const funboxFrequency = getFunboxWordsFrequency() ?? "normal";
  let randomWord = currentWordset.randomWord(funboxFrequency);
  const previousWordRaw = previousWord.replace(/[.?!":\-,]/g, "").toLowerCase();
  const previousWord2Raw = previousWord2
    ?.replace(/[.?!":\-,']/g, "")
    .toLowerCase();

  if (currentSection.length === 0) {
    const funboxSection = await getFunboxSection();

    if (Config.mode === "quote") {
      randomWord = currentWordset.nextWord();
    } else if (Config.mode === "custom" && CustomText.getMode() === "repeat") {
      randomWord = currentWordset.nextWord();
    } else if (
      Config.mode === "custom" &&
      CustomText.getMode() === "random" &&
      (currentWordset.length < 4 || PractiseWords.before.mode !== null)
    ) {
      randomWord = currentWordset.randomWord(funboxFrequency);
    } else if (Config.mode === "custom" && CustomText.getMode() === "shuffle") {
      randomWord = currentWordset.shuffledWord();
    } else if (
      Config.mode === "custom" &&
      CustomText.getLimitMode() === "section"
    ) {
      randomWord = currentWordset.randomWord(funboxFrequency);

      const previousSection = Arrays.nthElementFromArray(sectionHistory, -1);
      const previousSection2 = Arrays.nthElementFromArray(sectionHistory, -2);

      let regenerationCount = 0;
      while (
        regenerationCount < 100 &&
        (previousSection === randomWord || previousSection2 === randomWord)
      ) {
        regenerationCount++;
        randomWord = currentWordset.randomWord(funboxFrequency);
      }
    } else if (isCurrentlyUsingFunboxSection) {
      randomWord = funboxSection.join(" ");
    } else {
      let regenarationCount = 0; //infinite loop emergency stop button
      let firstAfterSplit = (randomWord.split(" ")[0] as string).toLowerCase();
      let firstAfterSplitLazy = applyLazyModeToWord(
        firstAfterSplit,
        currentLanguage,
      );
      while (
        regenarationCount < 100 &&
        (previousWordRaw === firstAfterSplitLazy ||
          previousWord2Raw === firstAfterSplitLazy ||
          (Config.mode !== "custom" &&
            !Config.punctuation &&
            randomWord === "I") ||
          (Config.mode !== "custom" &&
            !Config.punctuation &&
            !Config.language.startsWith("code") &&
            /[-=_+[\]{};'\\:"|,./<>?]/i.test(randomWord)) ||
          (Config.mode !== "custom" &&
            !Config.numbers &&
            /[0-9]/i.test(randomWord)))
      ) {
        regenarationCount++;
        randomWord = currentWordset.randomWord(funboxFrequency);
        firstAfterSplit = randomWord.split(" ")[0] as string;
        firstAfterSplitLazy = applyLazyModeToWord(
          firstAfterSplit,
          currentLanguage,
        );
      }
    }
    randomWord = randomWord.replace(/ +/g, " ");
    randomWord = randomWord.replace(/(^ )|( $)/g, "");

    randomWord = getFunboxWord(randomWord, wordIndex, currentWordset);

    currentSection = [...randomWord.split(" ")];
    sectionHistory.push(randomWord);
    randomWord = currentSection.shift() as string;
    sectionIndex++;
  } else {
    randomWord = currentSection.shift() as string;
  }

  if (randomWord === undefined) {
    throw new WordGenError("Random word is undefined");
  }

  if (randomWord === "") {
    throw new WordGenError("Random word is empty");
  }

  if (/ /g.test(randomWord)) {
    throw new WordGenError("Random word contains spaces");
  }

  const usingFunboxWithGetWord = isFunboxActiveWithFunction("getWord");
  const randomWordLanguage =
    (currentWordset instanceof PolyglotWordset
      ? currentWordset.wordsWithLanguage.get(randomWord)
      : Config.language) ?? Config.language; // Fall back to Config language if per-word language is unavailable

  if (
    Config.mode !== "custom" &&
    Config.mode !== "quote" &&
    /[A-Z]/.test(randomWord) &&
    !Config.punctuation &&
    !randomWordLanguage.startsWith("german") &&
    !randomWordLanguage.startsWith("swiss_german") &&
    !randomWordLanguage.startsWith("code") &&
    !randomWordLanguage.startsWith("klingon") &&
    !isCurrentlyUsingFunboxSection &&
    !usingFunboxWithGetWord
  ) {
    randomWord = randomWord.toLowerCase();
  }

  randomWord = randomWord.replace(/ +/gm, " ");
  randomWord = randomWord.replace(/(^ )|( $)/gm, "");
  randomWord = applyLazyModeToWord(randomWord, currentLanguage);

  if (Config.language.startsWith("swiss_german")) {
    randomWord = randomWord.replace(/ß/g, "ss");
  }

  if (
    Config.punctuation &&
    !currentLanguage.originalPunctuation &&
    !isCurrentlyUsingFunboxSection
  ) {
    randomWord = await punctuateWord(
      previousWord,
      randomWord,
      wordIndex,
      wordsBound,
    );
  }

  randomWord = await applyBritishEnglishToWord(randomWord, previousWordRaw);

  if (Config.numbers) {
    if (random() < 0.1) {
      randomWord = GetText.getNumbers(4);

      if (Config.language.startsWith("kurdish")) {
        randomWord = Misc.convertNumberToArabic(randomWord);
      } else if (Config.language.startsWith("nepali")) {
        randomWord = Misc.convertNumberToNepali(randomWord);
      } else if (Config.language.startsWith("bangla")) {
        randomWord = Misc.convertNumberToBangla(randomWord);
      } else if (Config.language.startsWith("hindi")) {
        randomWord = Misc.convertNumberToHindi(randomWord);
      }
    }
  }

  randomWord = applyFunboxesToWord(randomWord, wordIndex, wordsBound);

  console.debug("Word:", randomWord);

  const ret = {
    word: randomWord,
    sectionIndex: sectionIndex,
  };

  previousGetNextWordReturns.push(ret);

  return ret;
}
