MZ Tactics Selector

Adds a dropdown menu with overused tactics and lets you save your own tactics for quick access later on.

Version vom 05.08.2024. Aktuellste Version

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         MZ Tactics Selector
// @namespace    douglaskampl
// @version      7.4
// @description  Adds a dropdown menu with overused tactics and lets you save your own tactics for quick access later on.
// @author       Douglas Vieira
// @match        https://www.managerzone.com/?p=tactics
// @match        https://www.managerzone.com/?p=national_teams&sub=tactics&type=*
// @match        https://www.managerzone.com/?p=players
// @icon         https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @require      https://unpkg.com/[email protected]/dist/sha256.js
// @require      https://unpkg.com/[email protected]/i18next.min.js
// @require      https://cdn.jsdelivr.net/npm/sweetalert2@11
// @license      MIT
// ==/UserScript==

GM_addStyle(
  "@import url('https://fonts.googleapis.com/css2?family=Montserrat&display=swap');"
);

(function () {
  "use strict";

  let dropdownMenuTactics = [];

  const defaultTacticsDataUrl =
    "https://raw.githubusercontent.com/douglasdotv/tactics-selector/main/json/tactics.json?callback=?";

  let activeLanguage;

  const flagsDataUrl = {
    gb: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/gb.svg",
    br: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/br.svg",
    cn: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/cn.svg",
    se: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/se.svg",
    no: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/no.svg",
    dk: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/dk.svg",
    ar: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/ar.svg",
    pl: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/pl.svg",
    nl: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/nl.svg",
    id: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/id.svg",
    de: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/de.svg",
    it: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/it.svg",
    fr: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/fr.svg",
    ro: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/ro.svg",
    tr: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/tr.svg",
    kr: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/kr.svg",
    ru: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/ru.svg",
    sa: "https://raw.githubusercontent.com/lipis/flag-icons/d6785f2434e54e775d55a304733d17b048eddfb5/flags/4x3/sa.svg",
  };

  const languages = [
    { code: "en", name: "English", flag: flagsDataUrl.gb },
    { code: "pt", name: "Português", flag: flagsDataUrl.br },
    { code: "zh", name: "中文", flag: flagsDataUrl.cn },
    { code: "sv", name: "Svenska", flag: flagsDataUrl.se },
    { code: "no", name: "Norsk", flag: flagsDataUrl.no },
    { code: "da", name: "Dansk", flag: flagsDataUrl.dk },
    { code: "es", name: "Español", flag: flagsDataUrl.ar },
    { code: "pl", name: "Polski", flag: flagsDataUrl.pl },
    { code: "nl", name: "Nederlands", flag: flagsDataUrl.nl },
    { code: "id", name: "Bahasa Indonesia", flag: flagsDataUrl.id },
    { code: "de", name: "Deutsch", flag: flagsDataUrl.de },
    { code: "it", name: "Italiano", flag: flagsDataUrl.it },
    { code: "fr", name: "Français", flag: flagsDataUrl.fr },
    { code: "ro", name: "Română", flag: flagsDataUrl.ro },
    { code: "tr", name: "Türkçe", flag: flagsDataUrl.tr },
    { code: "ko", name: "한국어", flag: flagsDataUrl.kr },
    { code: "ru", name: "Русский", flag: flagsDataUrl.ru },
    { code: "ar", name: "العربية", flag: flagsDataUrl.sa },
  ];

  const strings = {
    addButton: "",
    deleteButton: "",
    renameButton: "",
    updateButton: "",
    clearButton: "",
    resetButton: "",
    importButton: "",
    exportButton: "",
    usefulLinksButton: "",
    aboutButton: "",
    tacticNamePrompt: "",
    addAlert: "",
    deleteAlert: "",
    renameAlert: "",
    updateAlert: "",
    clearAlert: "",
    resetAlert: "",
    deleteConfirmation: "",
    updateConfirmation: "",
    clearConfirmation: "",
    resetConfirmation: "",
    invalidTacticError: "",
    noTacticNameProvidedError: "",
    alreadyExistingTacticNameError: "",
    tacticNameLengthError: "",
    noTacticSelectedError: "",
    duplicateTacticError: "",
    modalContentInfoText: "",
    modalContentFeedbackText: "",
    usefulContent: "",
    tacticsDropdownMenuLabel: "",
    languageDropdownMenuLabel: "",
  };

  const elementStringKeys = {
    add_tactic_button: "addButton",
    delete_tactic_button: "deleteButton",
    rename_tactic_button: "renameButton",
    update_tactic_button: "updateButton",
    clear_tactics_button: "clearButton",
    reset_tactics_button: "resetButton",
    import_tactics_button: "importButton",
    export_tactics_button: "exportButton",
    about_button: "aboutButton",
    tactics_dropdown_menu_label: "tacticsDropdownMenuLabel",
    language_dropdown_menu_label: "languageDropdownMenuLabel",
    info_modal_info_text: "modalContentInfoText",
    info_modal_feedback_text: "modalContentFeedbackText",
    useful_links_button: "usefulLinksButton",
    useful_content: "usefulContent",
  };

  let infoModal;
  let usefulLinksModal;

  const tacticsBox = document.getElementById("tactics_box");

  const tacticsPreset = document.getElementById("tactics_preset");

  const outfieldPlayersSelector =
    ".fieldpos.fieldpos-ok.ui-draggable:not(.substitute):not(.goalkeeper):not(.substitute.goalkeeper), .fieldpos.fieldpos-collision.ui-draggable:not(.substitute):not(.goalkeeper):not(.substitute.goalkeeper)";

  const goalkeeperSelector = ".fieldpos.fieldpos-ok.goalkeeper.ui-draggable";

  const formationTextSelector = "#formation_text";

  const tacticSlotSelector =
    ".ui-state-default.ui-corner-top.ui-tabs-selected.ui-state-active.invalid";

  const minOutfieldPlayers = 10;

  const maxTacticNameLength = 50;

  async function initialize() {
    if (tacticsBox) {
      activeLanguage = getActiveLanguage();
      i18next
        .init({
          lng: activeLanguage,
          resources: {
            [activeLanguage]: {
              translation: await (
                await fetch(
                  `https://raw.githubusercontent.com/douglasdotv/tactics-selector/main/json/lang/${activeLanguage}.json?callback=?`
                )
              ).json(),
            },
          },
        })
        .then(() => {
          const tacticsSelectorDiv = createTacticsSelectorDiv();

          if (isFootball()) {
            insertAfterElement(tacticsSelectorDiv, tacticsBox);
          }

          const firstRow = createRow("tactics_selector_div_first_row");
          const secondRow = createRow("tactics_selector_div_second_row");

          appendChildren(tacticsSelectorDiv, [
            firstRow,
            secondRow,
            createHiddenTriggerButton(),
          ]);

          setupFirstRow();
          setupSecondRow();

          fetchTacticsFromGMStorage()
            .then((data) => {
              const tacticsDropdownMenu = document.getElementById(
                "tactics_dropdown_menu"
              );

              dropdownMenuTactics = data.tactics;
              dropdownMenuTactics.sort((a, b) => {
                return a.name.localeCompare(b.name);
              });

              addTacticsToDropdownMenu(
                tacticsDropdownMenu,
                dropdownMenuTactics
              );

              tacticsDropdownMenu.addEventListener("change", function () {
                handleTacticsSelection(this.value);
              });
            })
            .catch((err) => {
              console.error("Couldn't fetch data from json: ", err);
            });
          setModals();
          updateTranslation();
        });
    }
    applyUxxFilter();
  }

  window.addEventListener("load", function () {
    initialize().catch((err) => {
      console.error("Init error: ", err);
    });
  });

  // _____Tactics Dropdown Menu_____

  function createTacticsDropdownMenu() {
    const dropdown = document.createElement("select");
    setupDropdownMenu(dropdown, "tactics_dropdown_menu");
    appendChildren(dropdown, [createPlaceholderOption()]);
    return dropdown;
  }

  function createHiddenTriggerButton() {
    /*
     * The purpose of this button is to trigger the tactics preset change event: it changes the tactic to the default 5-3-2.
     * It's a workaround to put 10 players on the field when the user has less than 10 players on the field and selects a tactic.
     * If that happens, the pitch will be filled with 10 players in a 5-3-2 formation, then they will be rearranged to the selected tactic.
     */
    const button = document.createElement("button");
    button.id = "hidden_trigger_button";
    button.textContent = "";
    button.style.visibility = "hidden";

    button.addEventListener("click", function () {
      tacticsPreset.value = "5-3-2";
      tacticsPreset.dispatchEvent(new Event("change"));
    });

    return button;
  }

  async function fetchTacticsFromGMStorage() {
    const storedTactics = GM_getValue("ls_tactics");
    if (storedTactics) {
      return storedTactics;
    } else {
      const jsonTactics = await fetchTacticsFromJson();
      storeTacticsInGMStorage(jsonTactics);
      return jsonTactics;
    }
  }

  async function fetchTacticsFromJson() {
    const response = await fetch(defaultTacticsDataUrl);
    return await response.json();
  }

  function storeTacticsInGMStorage(data) {
    GM_setValue("ls_tactics", data);
  }

  function addTacticsToDropdownMenu(dropdown, tactics) {
    for (const tactic of tactics) {
      const option = document.createElement("option");
      option.value = tactic.name;
      option.text = tactic.name;
      dropdown.appendChild(option);
    }
  }

  function handleTacticsSelection(tactic) {
    const outfieldPlayers = Array.from(
      document.querySelectorAll(outfieldPlayersSelector)
    );

    const selectedTactic = dropdownMenuTactics.find(
      (tacticData) => tacticData.name === tactic
    );

    if (selectedTactic) {
      if (outfieldPlayers.length < minOutfieldPlayers) {
        const hiddenTriggerButton = document.getElementById(
          "hidden_trigger_button"
        );
        hiddenTriggerButton.click();
        setTimeout(() => rearrangePlayers(selectedTactic.coordinates), 1);
      } else {
        rearrangePlayers(selectedTactic.coordinates);
      }
    }
  }

  function rearrangePlayers(coordinates) {
    const outfieldPlayers = Array.from(
      document.querySelectorAll(outfieldPlayersSelector)
    );

    findBestPositions(outfieldPlayers, coordinates);

    for (let i = 0; i < outfieldPlayers.length; ++i) {
      outfieldPlayers[i].style.left = coordinates[i][0] + "px";
      outfieldPlayers[i].style.top = coordinates[i][1] + "px";
      removeCollision(outfieldPlayers[i]);
    }

    removeTacticSlotInvalidStatus();
    updateFormationText(getFormation(coordinates));
  }

  function findBestPositions(players, coordinates) {
    players.sort((a, b) => parseInt(a.style.top) - parseInt(b.style.top));
    coordinates.sort((a, b) => a[1] - b[1]);
  }

  function removeCollision(player) {
    if (player.classList.contains("fieldpos-collision")) {
      player.classList.remove("fieldpos-collision");
      player.classList.add("fieldpos-ok");
    }
  }

  function removeTacticSlotInvalidStatus() {
    const slot = document.querySelector(tacticSlotSelector);
    if (slot) {
      slot.classList.remove("invalid");
    }
  }

  function updateFormationText(formation) {
    const formationTextElement = document.querySelector(formationTextSelector);
    formationTextElement.querySelector(".defs").textContent =
      formation.defenders;
    formationTextElement.querySelector(".mids").textContent =
      formation.midfielders;
    formationTextElement.querySelector(".atts").textContent =
      formation.strikers;
  }

  function getFormation(coordinates) {
    let strikers = 0;
    let midfielders = 0;
    let defenders = 0;

    for (const coo of coordinates) {
      const y = coo[1];
      if (y < 103) {
        strikers++;
      } else if (y <= 204) {
        midfielders++;
      } else {
        defenders++;
      }
    }

    return { strikers, midfielders, defenders };
  }

  // _____Add new tactic_____

  function createAddNewTacticButton() {
    const button = document.createElement("button");
    setupButton(button, "add_tactic_button", strings.addButton);

    button.addEventListener("click", function () {
      addNewTactic().catch(console.error);
    });

    return button;
  }

  async function addNewTactic() {
    const outfieldPlayers = Array.from(
      document.querySelectorAll(outfieldPlayersSelector)
    );

    const tacticsDropdownMenu = document.getElementById(
      "tactics_dropdown_menu"
    );

    const tacticCoordinates = outfieldPlayers.map((player) => [
      parseInt(player.style.left),
      parseInt(player.style.top),
    ]);

    if (!validateTacticPlayerCount(outfieldPlayers)) {
      return;
    }

    const tacticId = generateUniqueId(tacticCoordinates);
    const isDuplicate = await validateDuplicateTactic(tacticId);
    if (isDuplicate) {
      Swal.fire({
        icon: 'error',
        title: 'Error',
        text: strings.duplicateTacticError,
      });
      return;
    }

    const tacticName = await Swal.fire({
      title: strings.tacticNamePrompt,
      input: 'text',
      inputValidator: (value) => {
        if (!value) {
          return strings.noTacticNameProvidedError;
        }
        if (value.length > maxTacticNameLength) {
          return strings.tacticNameLengthError;
        }
        if (dropdownMenuTactics.some((t) => t.name === value)) {
          return strings.alreadyExistingTacticNameError;
        }
      },
    }).then((result) => result.value);

    if (!tacticName) {
      return;
    }

    const tactic = {
      name: tacticName,
      coordinates: tacticCoordinates,
      id: tacticId,
    };

    saveTacticToStorage(tactic).catch(console.error);
    addTacticsToDropdownMenu(tacticsDropdownMenu, [tactic]);
    dropdownMenuTactics.push(tactic);

    tacticsDropdownMenu.value = tactic.name;
    handleTacticsSelection(tactic.name);

    Swal.fire({
      icon: 'success',
      title: 'Done',
      text: strings.addAlert.replace("{}", tactic.name),
    });
  }

  function validateTacticPlayerCount(outfieldPlayers) {
    const isGoalkeeper = document.querySelector(goalkeeperSelector);

    outfieldPlayers = outfieldPlayers.filter(
      (player) => !player.classList.contains("fieldpos-collision")
    );

    if (outfieldPlayers.length < minOutfieldPlayers || !isGoalkeeper) {
      Swal.fire({
        icon: 'error',
        title: 'Error',
        text: strings.invalidTacticError,
      });
      return false;
    }

    return true;
  }

  async function validateDuplicateTactic(id) {
    const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
    return tacticsData.tactics.some((tactic) => tactic.id === id);
  }

  async function saveTacticToStorage(tactic) {
    const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
    tacticsData.tactics.push(tactic);
    await GM_setValue("ls_tactics", tacticsData);
  }

  // _____Delete tactic_____

  function createDeleteTacticButton() {
    const button = document.createElement("button");
    setupButton(button, "delete_tactic_button", strings.deleteButton);

    button.addEventListener("click", function () {
      deleteTactic().catch(console.error);
    });

    return button;
  }

  async function deleteTactic() {
    const tacticsDropdownMenu = document.getElementById(
      "tactics_dropdown_menu"
    );

    const selectedTactic = dropdownMenuTactics.find(
      (tactic) => tactic.name === tacticsDropdownMenu.value
    );

    if (!selectedTactic) {
      Swal.fire({
        icon: 'error',
        title: 'Error',
        text: strings.noTacticSelectedError,
      });
      return;
    }

    const confirmed = await Swal.fire({
      title: 'Confirmation',
      text: strings.deleteConfirmation.replace("{}", selectedTactic.name),
      icon: 'warning',
      showCancelButton: true,
      confirmButtonText: 'Delete Tactic',
      cancelButtonText: 'Cancel',
    }).then((result) => result.isConfirmed);

    if (!confirmed) {
      return;
    }

    const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
    tacticsData.tactics = tacticsData.tactics.filter(
      (tactic) => tactic.id !== selectedTactic.id
    );

    await GM_setValue("ls_tactics", tacticsData);

    dropdownMenuTactics = dropdownMenuTactics.filter(
      (tactic) => tactic.id !== selectedTactic.id
    );

    const selectedOption = Array.from(tacticsDropdownMenu.options).find(
      (option) => option.value === selectedTactic.name
    );
    tacticsDropdownMenu.remove(selectedOption.index);

    if (tacticsDropdownMenu.options[0]?.disabled) {
      tacticsDropdownMenu.selectedIndex = 0;
    }

    Swal.fire({
      icon: 'success',
      title: 'Done',
      text: strings.deleteAlert.replace("{}", selectedTactic.name),
    });
  }

  // _____Rename tactic_____

  function createRenameTacticButton() {
    const button = document.createElement("button");
    setupButton(button, "rename_tactic_button", strings.renameButton);

    button.addEventListener("click", function () {
      renameTactic().catch(console.error);
    });

    return button;
  }

  async function renameTactic() {
    const tacticsDropdownMenu = document.getElementById(
      "tactics_dropdown_menu"
    );

    const selectedTactic = dropdownMenuTactics.find(
      (tactic) => tactic.name === tacticsDropdownMenu.value
    );

    if (!selectedTactic) {
      Swal.fire({
        icon: 'error',
        title: 'Error',
        text: strings.noTacticSelectedError,
      });
      return;
    }

    const newName = await Swal.fire({
      title: strings.tacticNamePrompt,
      input: 'text',
      inputValidator: (value) => {
        if (!value) {
          return strings.noTacticNameProvidedError;
        }
        if (value.length > maxTacticNameLength) {
          return strings.tacticNameLengthError;
        }
        if (dropdownMenuTactics.some((t) => t.name === value)) {
          return strings.alreadyExistingTacticNameError;
        }
      },
    }).then((result) => result.value);

    if (!newName) {
      return;
    }

    const selectedOption = Array.from(tacticsDropdownMenu.options).find(
      (option) => option.value === selectedTactic.name
    );

    const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
    tacticsData.tactics = tacticsData.tactics.map((tactic) => {
      if (tactic.id === selectedTactic.id) {
        tactic.name = newName;
      }
      return tactic;
    });

    await GM_setValue("ls_tactics", tacticsData);

    dropdownMenuTactics = dropdownMenuTactics.map((tactic) => {
      if (tactic.id === selectedTactic.id) {
        tactic.name = newName;
      }
      return tactic;
    });

    selectedOption.value = newName;
    selectedOption.textContent = newName;

    Swal.fire({
      icon: 'success',
      title: 'Done',
      text: strings.renameAlert.replace("{}", selectedTactic.name).replace("{}", newName),
    });
  }

  // _____Update tactic_____

  function createUpdateTacticButton() {
    const button = document.createElement("button");
    setupButton(button, "update_tactic_button", strings.updateButton);

    button.addEventListener("click", function () {
      updateTactic().catch(console.error);
    });

    return button;
  }

  async function updateTactic() {
    const outfieldPlayers = Array.from(
      document.querySelectorAll(outfieldPlayersSelector)
    );

    const tacticsDropdownMenu = document.getElementById(
      "tactics_dropdown_menu"
    );

    const selectedTactic = dropdownMenuTactics.find(
      (tactic) => tactic.name === tacticsDropdownMenu.value
    );

    if (!selectedTactic) {
      Swal.fire({
        icon: 'error',
        title: 'Error',
        text: strings.noTacticSelectedError,
      });
      return;
    }

    const updatedCoordinates = outfieldPlayers.map((player) => [
      parseInt(player.style.left),
      parseInt(player.style.top),
    ]);

    const newId = generateUniqueId(updatedCoordinates);

    const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
    const validationOutcome = await validateDuplicateTacticWithUpdatedCoord(
      newId,
      selectedTactic,
      tacticsData
    );

    switch (validationOutcome) {
      case "unchanged":
        return;
      case "duplicate":
        Swal.fire({
          icon: 'error',
          title: 'Error',
          text: strings.duplicateTacticError,
        });
        return;
      case "unique":
        break;
      default:
        return;
    }

    const confirmed = await Swal.fire({
      title: 'Confirmation',
      text: strings.updateConfirmation.replace("{}", selectedTactic.name),
      icon: 'warning',
      showCancelButton: true,
      confirmButtonText: 'Update',
      cancelButtonText: 'Cancel',
    }).then((result) => result.isConfirmed);

    if (!confirmed) {
      return;
    }

    for (const tactic of tacticsData.tactics) {
      if (tactic.id === selectedTactic.id) {
        tactic.coordinates = updatedCoordinates;
        tactic.id = newId;
      }
    }

    for (const tactic of dropdownMenuTactics) {
      if (tactic.id === selectedTactic.id) {
        tactic.coordinates = updatedCoordinates;
        tactic.id = newId;
      }
    }

    await GM_setValue("ls_tactics", tacticsData);

    Swal.fire({
      icon: 'success',
      title: 'Done',
      text: strings.updateAlert.replace("{}", selectedTactic.name),
    });
  }

  async function validateDuplicateTacticWithUpdatedCoord(
    newId,
    selectedTac,
    tacticsData
  ) {
    if (newId === selectedTac.id) {
      return "unchanged";
    } else if (tacticsData.tactics.some((tac) => tac.id === newId)) {
      return "duplicate";
    } else {
      return "unique";
    }
  }

  // _____Clear tactics_____

  function createClearTacticsButton() {
    const button = document.createElement("button");
    setupButton(button, "clear_tactics_button", strings.clearButton);

    button.addEventListener("click", function () {
      clearTactics().catch(console.error);
    });

    return button;
  }

  async function clearTactics() {
    const confirmed = await Swal.fire({
      title: 'Confirmation',
      text: strings.clearConfirmation,
      icon: 'warning',
      showCancelButton: true,
      confirmButtonText: 'Clear Tactics',
      cancelButtonText: 'Cancel',
    }).then((result) => result.isConfirmed);

    if (!confirmed) {
      return;
    }

    await GM_setValue("ls_tactics", { tactics: [] });
    dropdownMenuTactics = [];

    const tacticsDropdownMenu = document.getElementById(
      "tactics_dropdown_menu"
    );
    tacticsDropdownMenu.innerHTML = "";
    tacticsDropdownMenu.disabled = true;

    Swal.fire({
      icon: 'success',
      title: 'Done',
      text: strings.clearAlert,
    });
  }

  // _____Reset default settings_____

  function createResetTacticsButton() {
    const button = document.createElement("button");
    setupButton(button, "reset_tactics_button", strings.resetButton);

    button.addEventListener("click", function () {
      resetTactics().catch(console.error);
    });

    return button;
  }

  async function resetTactics() {
    const confirmed = await Swal.fire({
      title: 'Confirmation',
      text: strings.resetConfirmation,
      icon: 'warning',
      showCancelButton: true,
      confirmButtonText: 'Reset Tactics',
      cancelButtonText: 'Cancel',
    }).then((result) => result.isConfirmed);

    if (!confirmed) {
      return;
    }

    const response = await fetch(defaultTacticsDataUrl);
    const data = await response.json();
    const defaultTactics = data.tactics;

    await GM_setValue("ls_tactics", { tactics: defaultTactics });
    dropdownMenuTactics = defaultTactics;

    const tacticsDropdownMenu = document.getElementById(
      "tactics_dropdown_menu"
    );
    tacticsDropdownMenu.innerHTML = "";
    tacticsDropdownMenu.appendChild(createPlaceholderOption());
    addTacticsToDropdownMenu(tacticsDropdownMenu, dropdownMenuTactics);
    tacticsDropdownMenu.disabled = false;

    Swal.fire({
      icon: 'success',
      title: 'Done',
      text: strings.resetAlert,
    });
  }

  // _____Import/Export_____

  function createImportTacticsButton() {
    const button = document.createElement("button");
    setupButton(button, "import_tactics_button", strings.importButton);

    button.addEventListener("click", function () {
      importTactics().catch(console.error);
    });

    return button;
  }

  function createExportTacticsButton() {
    const button = document.createElement("button");
    setupButton(button, "export_tactics_button", strings.exportButton);
    button.addEventListener("click", exportTactics);
    return button;
  }

  async function importTactics() {
    const input = document.createElement("input");
    input.type = "file";
    input.accept = ".json";

    input.onchange = async function (event) {
      const file = event.target.files[0];
      const reader = new FileReader();

      reader.onload = async function (event) {
        const importedTactics = JSON.parse(event.target.result).tactics;

        let existingTactics = await GM_getValue("ls_tactics", { tactics: [] });
        existingTactics = existingTactics.tactics;

        const mergedTactics = [...existingTactics];
        for (const importedTactic of importedTactics) {
          if (
            !existingTactics.some((tactic) => tactic.id === importedTactic.id)
          ) {
            mergedTactics.push(importedTactic);
          }
        }

        await GM_setValue("ls_tactics", { tactics: mergedTactics });

        mergedTactics.sort((a, b) => a.name.localeCompare(b.name));
        dropdownMenuTactics = mergedTactics;

        const tacticsDropdownMenu = document.getElementById(
          "tactics_dropdown_menu"
        );
        tacticsDropdownMenu.innerHTML = "";
        tacticsDropdownMenu.append(createPlaceholderOption());
        addTacticsToDropdownMenu(tacticsDropdownMenu, dropdownMenuTactics);
        tacticsDropdownMenu.disabled = false;
      };

      reader.readAsText(file);
    };

    input.click();
  }

  function exportTactics() {
    const tactics = GM_getValue("ls_tactics", { tactics: [] });
    const tacticsJson = JSON.stringify(tactics);
    const blob = new Blob([tacticsJson], { type: "application/json" });
    const url = URL.createObjectURL(blob);

    const link = document.createElement("a");
    link.href = url;
    link.download = "tactics.json";
    link.click();

    URL.revokeObjectURL(url);
  }

  // _____Useful Links Button_____

  function createUsefulLinksButton() {
    const button = document.createElement("button");
    setupButton(button, "useful_links_button", strings.usefulLinksButton);

    button.addEventListener("click", function (event) {
      event.stopPropagation();
      toggleModal(usefulLinksModal);
    });

    return button;
  }

  function createUsefulLinksModal() {
    const modal = document.createElement("div");
    setupModal(modal, "useful_links_modal");

    const modalContent = createUsefulLinksModalContent();
    modal.appendChild(modalContent);

    return modal;
  }

  function createUsefulLinksModalContent() {
    const modalContent = document.createElement("div");
    styleModalContent(modalContent);

    const usefulContent = createUsefulContent();

    const hrefs = new Map([
      ["gewlaht - BoooM", "https://www.managerzone.com/?p=forum&sub=topic&topic_id=11415137&forum_id=49&sport=soccer"],
      ["honken91 - taktikskola", "https://www.managerzone.com/?p=forum&sub=topic&topic_id=12653892&forum_id=4&sport=soccer"],
      ["peto - mix de dibujos", "https://www.managerzone.com/?p=forum&sub=topic&topic_id=12196312&forum_id=255&sport=soccer"],
      ["The Zone Chile", "https://www.managerzone.com/thezone/paper.php?paper_id=18036&page=9&sport=soccer"],
    ]);
    const usefulLinksList = createLinksList(hrefs);

    modalContent.appendChild(usefulContent);
    modalContent.appendChild(usefulLinksList);

    return modalContent;
  }

  function createUsefulContent() {
    const usefulContent = document.createElement("p");
    usefulContent.id = "useful_content";
    usefulContent.textContent = strings.usefulContent;
    return usefulContent;
  }

  function createLinksList(hrefs) {
    const list = document.createElement("ul");

    hrefs.forEach((href, title) => {
      const listItem = document.createElement("li");
      const link = document.createElement("a");
      link.href = href;
      link.textContent = title;
      listItem.appendChild(link);
      list.appendChild(listItem);
    });

    return list;
  }

  function setUsefulLinksModal() {
    usefulLinksModal = createUsefulLinksModal();
    document.body.appendChild(usefulLinksModal);
  }

  // _____About button_____

  function createAboutButton() {
    const button = document.createElement("button");
    setupButton(button, "about_button", strings.aboutButton);

    button.addEventListener("click", function (event) {
      event.stopPropagation();
      toggleModal(infoModal);
    });

    return button;
  }

  function createInfoModal() {
    const modal = document.createElement("div");
    setupModal(modal, "info_modal");

    const modalContent = createModalContent();
    modal.appendChild(modalContent);

    return modal;
  }

  function createModalContent() {
    const modalContent = document.createElement("div");
    styleModalContent(modalContent);

    const title = createTitle();
    const infoText = createInfoText();
    const feedbackText = createFeedbackText();

    modalContent.appendChild(title);
    modalContent.appendChild(infoText);
    modalContent.appendChild(feedbackText);

    return modalContent;
  }

  function createTitle() {
    const title = document.createElement("h2");
    title.id = "info_modal_title";
    title.textContent = "MZ Tactics Selector";
    title.style.fontSize = "24px";
    title.style.fontWeight = "bold";
    title.style.marginBottom = "20px";
    return title;
  }

  function createInfoText() {
    const infoText = document.createElement("p");
    infoText.id = "info_modal_info_text";
    infoText.innerHTML = strings.modalContentInfoText;
    return infoText;
  }

  function createFeedbackText() {
    const feedbackText = document.createElement("p");
    feedbackText.id = "info_modal_feedback_text";
    feedbackText.innerHTML = strings.modalContentFeedbackText;
    return feedbackText;
  }

  function setInfoModal() {
    infoModal = createInfoModal();
    document.body.appendChild(infoModal);
  }

  // _____Audio button_____

  const createAudioButton = () => {
    const button = document.createElement("button");
    setupButton(button, "audio_button", "🔊");

    const audioUrls = [
      "https://ia802609.us.archive.org/16/items/w-w-w-d-e-e-p-d-i-v-e-c-o-m-wg7bkk/Webinar%E2%84%A2%20-%20w%20w%20w%20.%20d%20e%20e%20p%20d%20i%20v%20e%20.%20c%20o%20m%20-%2005%20URL%20%E6%B9%96.mp3",
      "https://ia802306.us.archive.org/15/items/remember-this-night-w6kvvl/COSMIC%20CYCLER%20-%20Remember%20This%20Night%20-%2009%20Night%20Breeze.mp3",
      "https://ia802300.us.archive.org/32/items/01.-dj-mixed-by-atsushi-ohara/Ridge%20Racer%20Soundtrack%20Collection/2006%20-%20Ridge%20Racers%202%20Direct%20Audio/11.%20Quiet%20Curves.mp3",
      "https://ia801701.us.archive.org/18/items/architectureintokyo-singles2013-2018/architecture%20in%20tokyo%20-%20singles%20%282013-2018%29%20-%2005%20marble%20%28ft.%20ULTRA%20%E3%82%A6%E3%83%AB%E3%83%88%E3%83%A9%29.mp3",
      "https://ia802306.us.archive.org/28/items/remember-this-night-w9vygv/COSMIC%20CYCLER%20-%20Remember%20This%20Night%20-%2006%20Living%20in%20Your%20Eyes.mp3",
      "https://ia801403.us.archive.org/24/items/ElisTom1AguasDeMarco/ElisTom1Aguas%20de%20marco.mp3",
      "https://ia800103.us.archive.org/18/items/azzahradibaku_gmail_Gana/Elis%20Regina%20e%20Adoniran%20Barbosa%20-%20Tiro%20ao%20%C3%81lvaro.mp3",
      "https://ia601404.us.archive.org/29/items/flying-beagle/The%20Second%20Summer.mp3",
      "https://ia801001.us.archive.org/25/items/101AirLaFemmeDargent/108-air_-_ce_matin_la.mp3",
      "https://ia804500.us.archive.org/7/items/special-night-w9vkyb/Cosmic%20Cycler%20-%20Special%20Night%20-%2003%20Trying%20to%20Relax.mp3",
    ];

    const audios = audioUrls.map(url => new Audio(url));

    let isPlaying = false;
    let currentAudio = null;

    button.addEventListener("click", function () {
      if (!isPlaying) {
        currentAudio = playRandomAudio(audios);
        isPlaying = true;
      } else {
        pauseAudio(currentAudio);
        isPlaying = false;
      }
      updateAudioIcon(button, isPlaying);
    });

    return button;
  }

  const playRandomAudio = (audios) => {
    if (audios.length === 0) {
      return;
    }

    const randomIdx = Math.floor(Math.random() * audios.length);
    const activeAudio = audios.splice(randomIdx, 1)[0];

    playAudio(activeAudio, audios);
    return activeAudio;
  }

  const playAudio = (currAudio, audios) => {
    currAudio.play();
    currAudio.onended = function () {
      playRandomAudio(audios);
    };
  }

  const pauseAudio = (audio) => {
    if (audio) {
      audio.pause();
      audio.currentTime = 0;
    }
  }

  const updateAudioIcon = (button, isPlaying) => {
    button.textContent = isPlaying ? "⏸️" : "🔊";
  }

  // _____Language Dropdown Menu_____

  function createLanguageDropdownMenu() {
    const dropdown = document.createElement("select");
    setupDropdownMenu(dropdown, "language_dropdown_menu");

    for (const lang of languages) {
      const option = document.createElement("option");
      option.value = lang.code;
      option.textContent = lang.name;
      if (lang.code === activeLanguage) {
        option.selected = true;
      }
      dropdown.appendChild(option);
    }

    dropdown.addEventListener("change", function () {
      changeLanguage(this.value).catch(console.error);
    });

    return dropdown;
  }

  async function changeLanguage(languageCode) {
    try {
      const translationDataUrl = `https://raw.githubusercontent.com/douglasdotv/tactics-selector/main/json/lang/${languageCode}.json?callback=?`;
      const translations = await (await fetch(translationDataUrl)).json();

      i18next.changeLanguage(languageCode);
      i18next.addResourceBundle(languageCode, "translation", translations);

      GM_setValue("language", languageCode);

      updateTranslation();

      const language = languages.find((lang) => lang.code === languageCode);
      if (language) {
        const flagImage = document.getElementById("language_flag");
        flagImage.src = language.flag;
      }
    } catch (err) {
      console.error(err);
    }
  }

  function updateTranslation() {
    for (const key in strings) {
      strings[key] = i18next.t(key);
    }

    for (const id in elementStringKeys) {
      const element = document.getElementById(id);
      if (id === "info_modal_info_text" || id === "info_modal_feedback_text") {
        element.innerHTML = strings[elementStringKeys[id]];
      } else {
        element.textContent = strings[elementStringKeys[id]];
      }
    }
  }

  function getActiveLanguage() {
    let language = GM_getValue("language");
    if (!language) {
      let browserLanguage = navigator.language || "en";
      browserLanguage = browserLanguage.split("-")[0];
      const languageExists = languages.some(
        (lang) => lang.code === browserLanguage
      );
      language = languageExists ? browserLanguage : "en";
    }
    return language;
  }

  // _____Other_____

  // note: apply Uxx filter was initially crafted by kostrzak16 (Yelonki). The applyUxxFilter() function is a slightly modified version of his code.
  function applyUxxFilter() {
    const minAge = 16;
    const maxAge = 21;
    let links = "";

    for (let i = minAge; i <= maxAge; ++i) {
      if (i !== minAge) {
        links += " ";
      }
      links += '<a href="#">' + i + "</a>";
    }

    $(".age-wrapper label").append(" " + links);

    let last = null;
    $(".age-wrapper label a").click(function () {
      const current = $(this).text().trim();

      if (last === current) {
        $("#age_from").val(current);
        $("#age_from").change();
      }

      $("#age_to").val(current);
      $("#age_to").change();

      if (parseInt($("#age_from").val()) > parseInt($("#age_to").val())) {
        $("#age_from").val($("#age_to").val());
        $("#age_from").change();
      }

      $("#filterSubmit").click();
      last = current;
    });
  }

  function isFootball() {
    const element = document.querySelector("div#tactics_box.soccer.clearfix");
    return !!element;
  }

  function appendChildren(parent, children) {
    children.forEach((ch) => {
      parent.appendChild(ch);
    });
  }

  function insertAfterElement(something, element) {
    element.parentNode.insertBefore(something, element.nextSibling);
  }

  function createTacticsSelectorDiv() {
    const div = document.createElement("div");
    setupTacticsSelectorDiv(div);
    return div;
  }

  function setupTacticsSelectorDiv(div) {
    div.id = "tactics_selector_div";
    div.style.width = "100%";
    div.style.display = "flex";
    div.style.flexDirection = "column";
    div.style.alignItems = "stretch";
    div.style.marginTop = "6px";
    div.style.marginLeft = "6px";
  }

  function createRow(id) {
    const row = document.createElement("div");
    row.id = id;
    row.style.display = "flex";
    row.style.justifyContent = "flex-start";
    row.style.flexWrap = "wrap";
    row.style.width = "96%";
    return row;
  }

  function setupFirstRow() {
    const firstRow = document.getElementById("tactics_selector_div_first_row");
    firstRow.style.display = "flex";
    firstRow.style.justifyContent = "space-between";
    firstRow.style.width = "77%";

    const tacticsDropdownMenuLabel = createDropdownMenuLabel(
      "tactics_dropdown_menu_label"
    );
    const tacticsDropdownMenu = createTacticsDropdownMenu();
    const tacticsDropdownGroup = createLabelDropdownMenuGroup(
      tacticsDropdownMenuLabel,
      tacticsDropdownMenu
    );

    const languageDropdownMenuLabel = createDropdownMenuLabel(
      "language_dropdown_menu_label"
    );
    const languageDropdownMenu = createLanguageDropdownMenu();
    const languageDropdownGroup = createLabelDropdownMenuGroup(
      languageDropdownMenuLabel,
      languageDropdownMenu
    );

    appendChildren(firstRow, [tacticsDropdownGroup, languageDropdownGroup]);

    appendChildren(languageDropdownGroup, [createFlagImage()]);
  }

  function setupSecondRow() {
    const secondRow = document.getElementById(
      "tactics_selector_div_second_row"
    );

    const addNewTacticBtn = createAddNewTacticButton();
    const deleteTacticBtn = createDeleteTacticButton();
    const renameTacticBtn = createRenameTacticButton();
    const updateTacticBtn = createUpdateTacticButton();
    const clearTacticsBtn = createClearTacticsButton();
    const resetTacticsBtn = createResetTacticsButton();
    const importTacticsBtn = createImportTacticsButton();
    const exportTacticsBtn = createExportTacticsButton();
    const usefulLinksBtn = createUsefulLinksButton();
    const aboutBtn = createAboutButton();
    const audioBtn = createAudioButton();

    appendChildren(secondRow, [
      addNewTacticBtn,
      deleteTacticBtn,
      renameTacticBtn,
      updateTacticBtn,
      clearTacticsBtn,
      resetTacticsBtn,
      importTacticsBtn,
      exportTacticsBtn,
      usefulLinksBtn,
      aboutBtn,
      audioBtn,
    ]);
  }

  function createDropdownMenuLabel(labelId) {
    const label = document.createElement("span");
    setupDropdownMenuLabel(label, labelId, strings.languageDropdownMenuLabel);
    return label;
  }

  function createLabelDropdownMenuGroup(label, dropdown) {
    const group = document.createElement("div");
    group.style.display = "flex";
    group.appendChild(label);
    group.appendChild(dropdown);
    return group;
  }

  function setupDropdownMenu(dropdown, id) {
    dropdown.id = id;
    dropdown.style.fontSize = "12px";
    dropdown.style.fontFamily = "Montserrat, sans-serif";
    dropdown.style.border = "none";
    dropdown.style.borderRadius = "6px";
    dropdown.style.backgroundColor = "#11112e";
    dropdown.style.color = "#e5e4e2";
    dropdown.style.padding = "3px 6px";
    dropdown.style.margin = "6px 0 6px 6px";
    dropdown.style.cursor = "pointer";
    dropdown.style.boxShadow = "0 2px 4px rgba(0, 0, 0, 0.2)";
    dropdown.style.outline = "none";
    dropdown.style.transition = "background-color 0.3s";

    dropdown.onmouseover = function () {
      dropdown.style.backgroundColor = "#334d77";
    };
    dropdown.onmouseout = function () {
      dropdown.style.backgroundColor = "#11112e";
    };
    dropdown.onfocus = function () {
      dropdown.style.outline = "2px solid #334d77";
    };
    dropdown.onblur = function () {
      dropdown.style.outline = "none";
    };
  }

  function setupDropdownMenuLabel(description, id, textContent) {
    description.id = id;
    description.textContent = textContent;
    description.style.fontFamily = "Montserrat, sans-serif";
    description.style.fontSize = "13px";
    description.style.color = "#11112e";
    description.style.margin = "6px 0 12px 6px";
  }

  function setupButton(button, id, textContent) {
    button.id = id;
    button.classList.add('mzbtn');
    button.textContent = textContent;
    button.style.fontFamily = "Montserrat, sans-serif";
    button.style.fontSize = "12px";
    button.style.color = "#e5e4e2";
    button.style.backgroundColor = "#11112e";
    button.style.padding = "4px 8px";
    button.style.marginLeft = "6px";
    button.style.marginTop = "6px";
    button.style.cursor = "pointer";
    button.style.border = "none";
    button.style.borderRadius = "6px";
    button.style.boxShadow = "0 2px 4px rgba(0, 0, 0, 0.2)";
    button.style.fontWeight = "500";
    button.style.transition = "background-color 0.3s, transform 0.3s";

    button.onmouseover = function () {
      button.style.backgroundColor = "#334d77";
      button.style.transform = "scale(1.05)";
    };
    button.onmouseout = function () {
      button.style.backgroundColor = "#11112e";
      button.style.transform = "scale(1)";
    };
    button.onfocus = function () {
      button.style.outline = "2px solid #334d77";
    };
    button.onblur = function () {
      button.style.outline = "none";
    };
  }

  function setModals() {
    setInfoModal();
    setUsefulLinksModal();
    setupModalsWindowClickListener();
  }

  function setupModalsWindowClickListener() {
    window.addEventListener("click", function (event) {
      if (usefulLinksModal.style.display === "block" && !usefulLinksModal.contains(event.target)) {
        hideModal(usefulLinksModal);
      }
      if (infoModal.style.display === "block" && !infoModal.contains(event.target)) {
        hideModal(infoModal);
      }
    });
  }

  function toggleModal(modal) {
    if (modal.style.display === "none" || modal.style.opacity === "0") {
      showModal(modal);
    } else {
      hideModal(modal);
    }
  }

  function showModal(modal) {
    modal.style.display = "block";
    setTimeout(function () {
      modal.style.opacity = "1";
    }, 0);
  }

  function hideModal(modal) {
    modal.style.opacity = "0";
    setTimeout(function () {
      modal.style.display = "none";
    }, 500);
  }

  function setupModal(modal, id) {
    modal.id = id;
    modal.style.display = "none";
    modal.style.position = "fixed";
    modal.style.zIndex = "1";
    modal.style.left = "50%";
    modal.style.top = "50%";
    modal.style.transform = "translate(-50%, -50%)";
    modal.style.opacity = "0";
    modal.style.transition = "opacity 0.5s ease-in-out";
  }

  function styleModalContent(content) {
    content.style.backgroundColor = "#fefefe";
    content.style.margin = "auto";
    content.style.padding = "20px";
    content.style.border = "1px solid #888";
    content.style.width = "80%";
    content.style.maxWidth = "500px";
    content.style.borderRadius = "10px";
    content.style.fontFamily = "Montserrat, sans-serif";
    content.style.textAlign = "center";
    content.style.color = "#000";
    content.style.fontSize = "16px";
    content.style.lineHeight = "1.5";
  }

  function createPlaceholderOption() {
    const placeholderOption = document.createElement("option");
    placeholderOption.value = "";
    placeholderOption.text = "";
    placeholderOption.disabled = true;
    placeholderOption.selected = true;
    return placeholderOption;
  }

  function createFlagImage() {
    const img = document.createElement("img");
    img.id = "language_flag";
    img.style.height = "15px";
    img.style.width = "25px";
    img.style.margin = "9px 0 6px 6px";
    const activeLang = languages.find((lang) => lang.code === activeLanguage);
    if (activeLang) {
      img.src = activeLang.flag;
    }
    return img;
  }

  function generateUniqueId(coordinates) {
    const sortedCoordinates = coordinates.sort(
      (a, b) => a[1] - b[1] || a[0] - b[0]
    );

    const coordString = sortedCoordinates
      .map((coord) => `${coord[1]}_${coord[0]}`)
      .join("_");

    return sha256Hash(coordString);
  }

  function sha256Hash(str) {
    const shaObj = new jsSHA("SHA-256", "TEXT");
    shaObj.update(str);
    const hash = shaObj.getHash("HEX");
    return hash;
  }
})();