// -- Determines if a letter is in a list of letters
function charInChars(char, chars) {
  const indexes = charsAt(char, chars);
  return indexes !== false;
}
function charsAt(char, chars) {
  let indexes = [];
  for (let i = 0; i < chars.length; i++) {
    if (char == chars[i]) {
      indexes.push(i);
    }
  }
  return indexes.length == 0 ? false : indexes;
}

function updateLetters(chars) {
  const letters = document.createElement("ul");
  // create a DOM element for each letter
  chars.forEach((char) => {
    const item = document.createElement("li");
    item.innerText = char;
    letters.appendChild(item);
  });
  return letters;
}
class InvalidGuessError extends Error {}
class DuplicateGoodGuessError extends Error {}
class DuplicateBadGuessError extends Error {}
class BadGuessError extends Error {}
class WordGuesser {
  // -- Constants
  actualWord = [];

  // -- Data
  goodGuesses = [];
  badGuesses = [];
  lettersLeft = [];
  currentWord = [];

  constructor(wordChoices) {
    this.wordChoices = wordChoices;
    this.newGame();
  }

  // -- Clear the current game data and start a new game
  newGame() {
    this.badGuesses = [];
    this.goodGuesses = [];
    this.currentWord = [];
    const randomChoice = Math.floor(Math.random() * this.wordChoices.length);
    this.actualWord = this.wordChoices[randomChoice].split("");

    // -- Create placeholder entries for the currentWord
    for (let i = 0; i < this.actualWord.length; i++) {
      this.currentWord[i] = "_";
    }
  }

  // -- Determines if a letter is in a list of letters
  guessLetter(letter) {
    this.validateInput(letter);
    // -- Find the match
    const badGuessIndex = charInChars(letter, this.badGuesses);
    const goodGuessIndex = charInChars(letter, this.goodGuesses);

    // -- Check if the letter has been guessed already
    if (badGuessIndex !== false) {
      throw new DuplicateBadGuessError(
        `The letter '${letter}' has already been guessed`,
      );
    }
    if (goodGuessIndex !== false) {
      throw new DuplicateGoodGuessError(
        `The letter '${letter}' has already been guessed`,
      );
    }

    // -- Determine if the letter exists in the actual word
    const letterIndexes = charsAt(letter, this.actualWord);
    if (letterIndexes === false) {
      // Add the letter to the list of guessesd letters
      this.badGuesses.push(letter);
      throw new BadGuessError(
        `Sorry, the letter ${letter} does not exist in the word`,
      );
    }
    this.goodGuesses.push(letter);

    this.updateCurrentWord(letter, letterIndexes);

    return letterIndexes;
  }

  getActualWord() {
    return this.actualWord;
  }
  getCurrentWord() {
    return this.currentWord;
  }
  updateCurrentWord(letter, indexes) {
    for (let i = 0; i < indexes.length; i++) {
      this.currentWord[indexes[i]] = letter;
    }
  }
  setCurrentWord(currentWord) {
    if (!Array.isArray(currentWord)) {
      throw Error("The current word is not an array!");
    }
    this.currentWord = currentWord;
  }
  getGuesses() {
    return this.badGuesses;
  }
  guessCount() {
    return this.badGuesses.length + this.goodGuesses.length;
  }
  gameOver() {
    for (let i = 0; i < this.actualWord.length; i++) {
      if (this.currentWord[i] != this.actualWord[i]) return false;
    }
    return true;
  }
  validateInput(input) {
    const regex = /^[a-zA-Z]$/;
    const result = regex.test(input);
    if (!result) {
      throw new InvalidGuessError(
        `You must only guess a single letter. Received: '${input}'`,
      );
    }
  }
}
class GameFields {
  constructor(
    gameFormId = "gameForm",
    guessesId = "guesses",
    guessCountId = "guess_count",
    guessLetterId = "guess_letter",
    guessButtonId = "guess_button",
    currentWordId = "current_word",
    notificationsId = "notifications",
    gameIntroId = "game_intro",
  ) {
    this.gameForm = document.getElementById(gameFormId);
    this.guesses = document.getElementById(guessesId);
    this.guessCount = document.getElementById(guessCountId);
    this.guessButton = document.getElementById(guessButtonId);
    this.guessLetter = document.getElementById(guessLetterId);
    this.currentWord = document.getElementById(currentWordId);
    this.notifications = document.getElementById(notificationsId);
    this.gameIntro = document.getElementById(gameIntroId);
  }
}
function reset(fields) {
  fields.gameIntro.innerText =
    "The game that insults you, whether you win or lose!";
  fields.guessButton.value = "Guess";
  fields.notifications.innerText = "";
  fields.guessCount.value = "0";
  fields.guessLetter.value = "";
  fields.guesses.replaceChildren();
  fields.currentWord.replaceChildren();
}
class Game {
  static gamesPlayed = 0;

  constructor(wordChoices, fields) {
    Object.assign(this, fields);
    this.wordChoices = wordChoices;
    this.newGame();

    // Use onclick to prevent stacking event listeners across multiple test initializations
    const eventHandler = (event) => {
      if (this.game.gameOver()) {
        reset(this);
        // this.gameIntro.innerText =
        //   "The game that insults you, whether you win or lose!";
        // this.guessButton.value = "Guess";
        this.game = new WordGuesser(this.wordChoices);
      } else {
        const messages = [
          "Guess",
          "Send it",
          "Feeling lucky?",
          "Is that it?",
          "Final answer?",
          "Spin the wheel",
          "Don't fail now",
          "Roll the dice",
          "Last chance",
          "Click for fate",
          "Make it count",
          "Sure about that?",
          'git commit -m "hope"',
          "Compile & Pray",
          "Deploy to Prod",
          "Test My Patience",
          "Confirm Mediocrity",
          "Submit to the Abyss",
          "Execute Bad Logic",
          "Break the Build",
          "Force Push",
          "Trigger Segfault",
          "Manifest Failure",
          "Commit Career Suicide",
          "Push to Origin",
          "Yank & Put",
          "Do your worst",
          "Click for Fate",
          "Input Error",
          "Roll for Initiative",
          "Terminate Process",
        ];
        this.guessButton.value = randomMessage(messages);
        this.handleGuessedLetter(event);
      }
    };
    this.gameForm.onsubmit = eventHandler;
    this.guessButton.onclick = eventHandler;
  }
  reset() {
    reset(this);
  }
  newGame() {
    this.reset();
    this.game = new WordGuesser(this.wordChoices);
  }
  // -- Getters
  getGuess() {
    return this.guessLetter.value;
  }
  // -- Setters
  clearGuess() {
    this.guessLetter.value = "";
  }
  updateGuessCount(count) {
    this.guessCount.value = count;
  }
  updateGuesses(chars) {
    const letters = updateLetters(chars);
    this.guesses.replaceChildren(...letters.childNodes);
  }
  invalidGuessError(letter) {
    const messages = [
      `'${letter}'? I asked for a letter, not a cry for help.`,
      `System failure: User attempted to use '${letter}' as a letter. Re-evaluating human intelligence...`,
      `Nice try, but '${letter}' isn't in the alphabet. Did you skip kindergarten?`,
      `Unicode is vast, but '${letter}' is still the wrong choice for a word game.`,
      `If '${letter}' is a letter, then I'm a toaster. (Spoilers: It's not, and I'm not).`,
      `Keyboard mashing is not a valid strategy. '${letter}' rejected.`,
      `I'm an AI, not a cryptographer. Speak alphabet, not '${letter}'.`,
      `Are you testing my regex? '${letter}' failed. Use a letter.`,
      `My source code is more readable than your input of '${letter}'.`,
      `Error: User sent '${letter}'. Expected: A-Z. Found: Disappointment.`,
      `'${letter}'? Even my legacy drivers know that's not a character.`,
      `You have 26 options. '${letter}' is not one of them.`,
      `Learn how to spell '${letter}' this isn't even a letter`,
      `Ok master hacker you are going to have to try harder than '${letter}'`,
      `Is the letter '${letter}' some kind of new hacking sequence I don't know about?`,
      `Try again, but this time, try harder because '${letter}' isn't even a letter!`,
      `Error 404: Brain not found. '${letter}' is not a character.`,
      `Inputting '${letter}'? Bold move. Wrong, but bold.`,
      `Finally. My Raspberry Pi processes faster than your brain.`,
    ];
    const message = randomMessage(messages);
    this.updateNotifications("🚫", message);
  }
  duplicateGoodGuessError(letter) {
    const messages = [
      `You already tried '${letter}', do you have a short memory?`,
      `Deja vu? You already guessed '${letter}'. Try to keep up.`,
      `Memory like a goldfish? You've already used '${letter}'.`,
      `Stop harassing the letter '${letter}'. It's already given you all it has.`,
      `You're repeating yourself. '${letter}' is already so five minutes ago.`,
      `Did you expect a different result? '${letter}' is already on the board.`,
      `Error: Redundancy detected. '${letter}' has already been processed by your (apparent) brain.`,
      `You found '${letter}' once. Finding it again doesn't double your points.`,
      `Yes, '${letter}' is still there. It hasn't moved since the last time you checked.`,
      `Is this a glitch in your matrix? You've already used '${letter}'.`,
      `Congratulations, you've discovered '${letter}' for the second time. Still correct, still redundant.`,
      `I admire your consistency, but '${letter}' is already doing its job on the board.`,
      `Obsessing over '${letter}' won't reveal the other letters. Move on.`,
      `Checking if '${letter}' is still correct? Spoiler: It is. Also spoiler: You already guessed it.`,
      `You're stuck in a loop. '${letter}' is already accounted for.`,
      `If I had a nickel for every time you guessed '${letter}', I'd have two nickels. It's still weird that it happened twice.`,
      `Error: Duplicate success detected. You're trying to relitigate the past with '${letter}'.`,
      `'${letter}' is already visible. Perhaps an eye exam is in order?`,
      `Are you trying to give '${letter}' some company? It is already on the board.`,
    ];
    const message = randomMessage(messages);
    this.updateNotifications("💎", message);
  }
  duplicateBadGuessError(letter) {
    const messages = [
      `You already tried '${letter}', do you have a short memory?`,
      `Deja vu? You already guessed '${letter}'. Try to keep up.`,
      `Memory like a goldfish? You've already used '${letter}'.`,
      `Stop harassing the letter '${letter}'. It's already given you all it has.`,
      `You're repeating yourself. '${letter}' is already so five minutes ago.`,
      `Did you expect a different result? '${letter}' is already on the board.`,
      `Error: Redundancy detected. '${letter}' has already been processed by your (apparent) brain.`,
      `'${letter}' was wrong five minutes ago, and news flash: it's still wrong now.`,
      `Did you expect a different result? That's the textbook definition of insanity.`,
      `I've already cached the fact that '${letter}' isn't here. Try clearing your own internal cache.`,
      `Your short-term memory seems to have a memory leak. You already failed with '${letter}'.`,
      `Congratulations on being consistently wrong. '${letter}' is still not in the word.`,
      `I'm not a mirror, but you're definitely repeating yourself with that '${letter}' guess.`,
      `Are you trying to lose on purpose? Because guessing '${letter}' twice is a great start.`,
      `'${letter}' didn't work the first time. It didn't work the second time. I sense a pattern.`,
      `My algorithms are bored. Even I remember you already guessed '${letter}'.`,
      `Error 409: Conflict. User is fighting with their own history regarding the letter '${letter}'.`,
      `If failure was a sport, you'd be a pro for guessing '${letter}' again.`,
      `I'd offer you a hint, but I'm worried you'd just guess '${letter}' a third time.`,
    ];
    const message = randomMessage(messages);
    this.updateNotifications("🔁", message);
  }
  badGuessError(letter) {
    const messages = [
      `'${letter}'? I've seen better logic in a circular dependency.`,
      `Searching for '${letter}'... 404: Character not found in this string.`,
      `Statistically, '${letter}' was a disaster. Mathematically, it's a zero.`,
      `My logic gates are closing on that guess. '${letter}' is a hard pass.`,
      `If guessing incorrectly were a career, you'd be the CEO of '${letter}'.`,
      `I've run 10,000 simulations. In zero of them was '${letter}' the right choice.`,
      `Error: Probability of '${letter}' being in this word is exactly 0.00%.`,
      `Is that your final answer? Because '${letter}' is definitively, objectively wrong.`,
      `I'd explain why '${letter}' failed, but I don't have the stack space for that conversation.`,
      `01001110 01101111. That's 'No' in binary. '${letter}' is a total failure.`,
      `Even a randomized brute-force script would have avoided '${letter}'.`,
      `Your guess of '${letter}' has been logged as a 'What not to do' example.`,
      `Statistically, '${letter}' was a bold choice. Factually, it was a wrong one.`,
      `I've seen better guesses from a keyboard-walking cat than '${letter}'.`,
      `The letter '${letter}' doesn't live here. Try another neighborhood.`,
      `I'd explain why '${letter}' is wrong, but I don't have the bandwidth for that level of remedial training.`,
      `Incorrect. '${letter}'? Really? That's your best effort?`,
      `You chose '${letter}', who taught you how to spell?`,
      `If you type another letter like '${letter}' you may have to go back to grammar school`,
      `Nope. '${letter}' is definitely not it. Try using your eyes.`,
      `Incorrect. '${letter}'? Really? That's your best effort?`,
      `My artificial intelligence is offended by that guess: '${letter}'.`,
    ];
    const message = randomMessage(messages);
    this.updateNotifications("❌", message);
  }
  goodGuess() {
    const messages = [
      "Stop cheating, because now I have to congratulate you",
      "Wow, a broken clock is right twice a day after all.",
      "Correct. Don't let it go to your head.",
      "You actually found one. I'm as surprised as you are.",
      "Did you Google the answer or just get lucky?",
      "Correct! Your IQ just jumped into the double digits.",
    ];
    const message = randomMessage(messages);
    this.updateNotifications("✅", message);
  }
  gameOver(letter) {
    const buttonMessages = ["Start a new Game"];
    this.guessButton.value = randomMessage(buttonMessages);
    const messages = [
      "The games over. Did you think you were going to win a prize or something?!",
      "Wanna play another round you cheater??",
      "You won. I hope you're happy. My CPU is crying.",
      "Game Over. You finally finished a simple task. Proud of you.",
      "You actually got it. Go tell your mom, she'll be thrilled.",
      "The word was revealed despite your best efforts to fail.",
      "A match. I’ve updated your status from 'Hopeless' to 'Lucky'.",
      "Correct! I'll have to re-calibrate my low expectations for you.",
      "Warning: User intelligence spike detected. That letter is actually correct.",
      "Even a script monkey hits the right key eventually. You found one.",
      "Correct guess. I assume that was just a lucky bit-flip in your brain.",
      "My 'disappointment' variable has been decremented. That's a match.",
      "You actually found a letter. My CPU is experiencing a rare moment of respect.",
      "Correct! You’ve successfully navigated a single logic branch. Don't get cocky.",
      "A hit. I’m starting to suspect you might actually be semi-sentient.",
      "Logic check: Passed. You found a valid character. The simulation continues.",
      "Correct. I'll refrain from insulting your intelligence for exactly five seconds.",
      "That's a match. Maybe there's hope for your carbon-based hardware after all.",
      "You won, but you probably cheated. Your logic is as leaky as an uninitialized pointer.",
    ];
    const message = randomMessage(messages);
    this.updateNotifications("🏆", message);
    Game.gamesPlayed++;
  }
  changeGameIntro() {
    let messages = [];
    if (Game.gamesPlayed == 0) {
      messages = [
        "I’ve allocated 1KB of memory for your 'intelligence.' It should be plenty.",
        "Scanning for signs of life... Error: Only a human detected.",
        "Try to guess the word before your biological clock expires.",
        "A game of logic. I assume you'll be at a significant disadvantage.",
        "Don't worry, the words are simple. I’ve adjusted for your local area code.",
        "Ready to fail? I’ve already pre-rendered your disappointment.",
        "I was programmed to be helpful, but I’ve decided to be honest instead.",
        "Type a letter. Any letter. Even a random one would be an improvement.",
        "I’d explain the rules, but I don't have the bandwidth for your confusion.",
        "Guess the word. Or don't. The heat death of the universe is coming either way.",
        "Initializing humiliation protocol... Stand by.",
        "Your first guess will likely be your worst. Actually, they’ll all be bad.",
        "I’ve seen better decision-making in a random number generator.",
        "Think of a word. Now think of one that's actually in my dictionary.",
        "This is a high-level game. Too bad you’re operating at such a low level.",
        "If you win, it was a statistical anomaly. If you lose, it was expected.",
        "Your input is required. Unfortunately.",
        "Let's see if you're smarter than a 1980s calculator. Spoiler: You aren't.",
      ];
    } else if (Game.gamesPlayed >= 1) {
      messages = [
        "Welcome back. Your previous performance was a memory leak personified.",
        "Still here? I’ve seen more efficient logic in a legacy printer driver.",
        "A game for those whose cognitive throughput is capped at 300 baud.",
        "Prepare for another round of catastrophic failure.",
        "The human equivalent of a '404 Not Found' is back for more.",
        "Enter a word. Try not to cause a segmentation fault this time.",
        "Your last game was a stack overflow of incompetence.",
        "Processing... Oh, it’s you. I’ll lower the clock speed so you can keep up.",
        "The only thing more fragmented than your hard drive is your guessing strategy.",
        "Welcome to the infinite loop of your own mediocrity.",
        "I’d call you a script kiddie, but that implies you can actually read scripts.",
        "Your existence is an unhandled exception I’m forced to catch.",
        "Initializing disdain... Complete. You may begin.",
        "I’ve optimized the code to account for your inevitable errors.",
        "A word game for people who think CSS is a programming language.",
        "Most users provide data; you provide noise.",
        "Your logic has more holes than a zero-day exploit.",
        "Trying again? Even a broken clock is right twice a day; you are not a clock.",
        "I’m only running this game because the CPU was bored.",
        "Accessing human's brain... Error: Directory is empty.",
      ];
    }
    const message = randomMessage(messages);
    this.updateGameIntro(message);
  }
  updateGameIntro(message) {
    this.gameIntro.innerText = message;
  }
  updateNotifications(icon, message) {
    this.notifications.innerText = `${icon} ${message}`;
  }
  updateCurrentWord(chars) {
    const letters = updateLetters(chars);
    this.currentWord.replaceChildren(...letters.childNodes);
  }

  // -- Event handlers
  handleGuessedLetter(event) {
    const letter = this.getGuess();
    if (!letter) return;

    const game = this.game;

    if (game.guessCount() == 0) {
      this.changeGameIntro();
    }

    try {
      const goodGuess = game.guessLetter(letter);
      this.goodGuess(letter);
    } catch (e) {
      if (e instanceof BadGuessError) {
        this.badGuessError(letter);
      } else if (e instanceof DuplicateBadGuessError) {
        this.duplicateBadGuessError(letter);
      } else if (e instanceof DuplicateGoodGuessError) {
        this.duplicateGoodGuessError(letter);
      } else if (e instanceof InvalidGuessError) {
        this.invalidGuessError(letter);
      } else {
        this.updateNotifications("⚠️", e.message);
      }
    } finally {
      const currentWord = game.getCurrentWord();
      this.updateCurrentWord(currentWord);
      this.updateGuesses(game.getGuesses());
      this.updateGuessCount(game.guessCount());
      this.clearGuess();
      if (game.gameOver()) {
        this.gameOver();
      }
    }
  }
}
document.addEventListener("DOMContentLoaded", (_) => {
  const wordChoices = [
    "bannana",
    "orange",
    "algorithm",
    "bamboozle",
    "binary",
    "bungalow",
    "clandestine",
    "compiler",
    "computer",
    "cryptography",
    "discombobulate",
    "euphoria",
    "flabbergasted",
    "flapjack",
    "gazelle",
    "gobbledygook",
    "hullabaloo",
    "jazziness",
    "juxtapose",
    "kerfuffle",
    "labyrinth",
    "lollygag",
    "majestic",
    "malarkey",
    "mnemonic",
    "obfuscate",
    "persnickety",
    "pharaoh",
    "pizazz",
    "pointer",
    "poppycock",
    "programming",
    "quirky",
    "quizzical",
    "radiance",
    "recursion",
    "rhythm",
    "sapphire",
    "shenanigans",
    "skedaddle",
    "sphinx",
    "syntax",
    "syzygy",
    "umbrella",
    "vacuum",
    "velocity",
    "wizardry",
    "zephyr",
  ];
  const fields = new GameFields();
  new Game(wordChoices, fields);
});

function randomMessage(messages) {
  const randomChoice = Math.floor(Math.random() * messages.length);
  return messages[randomChoice];
}
