Greasy Fork

MZ Tactics Manager

Userscript to manage tactics in ManagerZone

目前为 2025-03-24 提交的版本。查看 最新版本

// ==UserScript==
// @name         MZ Tactics Manager
// @namespace    douglaskampl
// @version      12.0.1
// @description  Userscript to manage tactics in ManagerZone
// @author       Douglas Vieira
// @match        https://www.managerzone.com/?p=tactics
// @match        https://www.managerzone.com/?p=national_teams&sub=tactics&type=*
// @icon         https://yt3.googleusercontent.com/ytc/AIdro_mDHaJkwjCgyINFM7cdUV2dWPPnL9Q58vUsrhOmRqkatg=s160-c-k-c0x00ffffff-no-rj
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_addStyle
// @require      https://cdnjs.cloudflare.com/ajax/libs/jsSHA/3.3.1/sha256.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/i18next/23.7.16/i18next.min.js
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // ==============================
    // STYLES
    // ==============================

    GM_addStyle(`@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600&display=swap");@import url("https://fonts.googleapis.com/css2?family=Dancing+Script:wght@500&display=swap");:root{--bg-color:#1e2028;--text-color:#e0e5ec;--highlight-color:#ff9933;--accent-color:#4f7cac;--shadow-color-dark:rgba(0,0,0,0.7);--shadow-color-light:rgba(59,66,82,0.5);--border-radius:16px;--neu-shadow-flat:6px 6px 12px var(--shadow-color-dark),-6px -6px 12px var(--shadow-color-light);--neu-shadow-pressed:inset 4px 4px 8px var(--shadow-color-dark),inset -4px -4px 8px var(--shadow-color-light);--neu-shadow-concave:6px 6px 12px var(--shadow-color-dark),-6px -6px 12px var(--shadow-color-light),inset 1px 1px 2px var(--shadow-color-light),inset -1px -1px 2px var(--shadow-color-dark);--short-passing-color:#4682B4;--wing-play-color:#3CB371;--other-style-color:#9370DB;--uncategorized-color:#888888;}#mz_tactics_panel{font-family:"Space Grotesk",-apple-system,sans-serif;background-color:var(--bg-color);border-radius:var(--border-radius);padding:24px;margin:12px;box-shadow:var(--neu-shadow-flat);border:none;transition:all 0.3s ease-in-out;max-height:1000px;opacity:1;color:var(--text-color);overflow:hidden;}#mz_tactics_panel.collapsed{max-height:0;padding:0;margin:0;opacity:0;border:none;}.mz-group{background-color:var(--bg-color);border-radius:var(--border-radius);padding:20px;margin:12px 0;box-shadow:var(--neu-shadow-concave);border:none;position:relative;}.mz-group-main-title{display:flex;justify-content:space-between;align-items:center;color:var(--text-color);font-size:18px;font-weight:500;margin:-4px 0 16px 0;padding-bottom:10px;border-bottom:1px solid rgba(51,51,51,0.1);}.mz-main-title{color:var(--text-color);font-family:"Space Grotesk",sans-serif;font-size:20px;font-weight:500;margin:0;padding:0;text-align:center;letter-spacing:0.2px;}.mz-version-text{color:var(--highlight-color);font-family:"Dancing Script",cursive;font-size:1em;font-weight:500;margin-left:6px;}.mz-divider{width:50px;height:2px;background:var(--text-color);margin:10px auto 0;opacity:0.2;}#toggle_panel_btn{background:var(--bg-color);border:none;color:var(--text-color);cursor:pointer;padding:8px;width:32px;height:32px;border-radius:50%;box-shadow:var(--neu-shadow-flat);margin-left:auto;font-size:18px;transition:all 0.3s ease;display:inline-flex;align-items:center;justify-content:center;}#toggle_panel_btn:hover{box-shadow:var(--neu-shadow-pressed);}#toggle_panel_btn.collapsed{transform:rotate(180deg);}#toggle_panel_btn.collapsed:hover{transform:rotate(180deg);box-shadow:var(--neu-shadow-pressed);}#collapsed_icon{position:fixed;top:20px;right:20px;background:var(--bg-color);border-radius:50%;width:48px;height:48px;display:flex;align-items:center;justify-content:center;cursor:pointer;opacity:0;transition:all 0.3s ease;transform:scale(0);box-shadow:var(--neu-shadow-flat);z-index:1000;color:var(--text-color);font-size:20px;}#collapsed_icon.visible{opacity:1;transform:scale(1);}#collapsed_icon:hover{transform:scale(1.05);box-shadow:var(--neu-shadow-pressed);}#mz_tactics_panel .mzbtn{display:inline-flex;align-items:center;justify-content:center;padding:10px 16px;margin:6px;font-family:"Space Grotesk",sans-serif;font-size:14px;font-weight:500;color:var(--text-color);background:var(--bg-color);border:none;border-radius:10px;cursor:pointer;transition:all 0.3s ease;box-shadow:var(--neu-shadow-flat);}#mz_tactics_panel .mzbtn:hover{box-shadow:var(--neu-shadow-pressed);transform:translateY(-1px);}#mz_tactics_panel .mzbtn:active{box-shadow:var(--neu-shadow-pressed);transform:translateY(0);}#mz_tactics_panel select{font-family:"Space Grotesk",sans-serif;font-size:14px;color:var(--text-color);padding:10px 16px;border:none;border-radius:10px;background-color:var(--bg-color);cursor:pointer;margin:6px 0;transition:all 0.3s ease;box-shadow:var(--neu-shadow-flat);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-image:url("data:image/svg+xml;utf8,<svg fill='%23e0e5ec' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/></svg>");background-repeat:no-repeat;background-position:right 10px top 50%;padding-right:30px;width:100%;}#mz_tactics_panel select:hover{box-shadow:var(--neu-shadow-pressed);}#mz_tactics_panel select:focus{outline:none;box-shadow:var(--neu-shadow-pressed);}.tactics-selector-section{margin-bottom:16px;}.tactics-selector-label{font-size:16px;font-weight:500;margin-bottom:10px;display:block;}#language_flag{height:12px;width:16px;margin:6px;border:none;border-radius:6px;box-shadow:var(--neu-shadow-flat);}#info_modal,#useful_links_modal{background:var(--bg-color);padding:24px;border-radius:var(--border-radius);color:var(--text-color);width:90%;max-width:500px;box-shadow:var(--neu-shadow-flat);}#info_modal a,#useful_links_modal a{color:#70a9ff;text-decoration:none;transition:color 0.3s ease;}#info_modal a:hover,#useful_links_modal a:hover{color:#97c4ff;}#info_modal ul,#useful_links_modal ul{list-style:none;padding:0;}#info_modal ul li,#useful_links_modal ul li{margin:12px 0;padding:8px 12px;border-radius:8px;background:var(--bg-color);box-shadow:var(--neu-shadow-flat);transition:all 0.3s ease;}#info_modal ul li:hover,#useful_links_modal ul li:hover{box-shadow:var(--neu-shadow-pressed);}#mz-modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:rgba(30,32,40,0.8);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:10000;opacity:0;transition:opacity 0.3s ease;}#mz-modal-container{background:var(--bg-color);border-radius:var(--border-radius);padding:24px;box-shadow:var(--neu-shadow-flat);border:none;max-width:500px;width:90%;transform:scale(0.9);transition:transform 0.3s ease;color:var(--text-color);font-family:"Space Grotesk",-apple-system,sans-serif;}#mz-modal-overlay.active{opacity:1;}#mz-modal-overlay.active #mz-modal-container{transform:scale(1);}#mz-modal-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;border-bottom:1px solid rgba(51,51,51,0.1);padding-bottom:12px;}#mz-modal-title{font-size:20px;font-weight:500;margin:0;}#mz-modal-close{background:var(--bg-color);border:none;color:var(--text-color);font-size:22px;cursor:pointer;transition:all 0.3s ease;padding:0;width:36px;height:36px;display:flex;align-items:center;justify-content:center;border-radius:50%;box-shadow:var(--neu-shadow-flat);}#mz-modal-close:hover{box-shadow:var(--neu-shadow-pressed);}#mz-modal-content{margin-bottom:24px;white-space:pre-line;line-height:1.5;}#mz-modal-input{width:calc(100% - 32px);background:var(--bg-color);border:none;color:var(--text-color);padding:14px 16px;border-radius:10px;font-family:"Space Grotesk",sans-serif;font-size:15px;margin-bottom:20px;transition:all 0.3s ease;box-sizing:border-box;box-shadow:var(--neu-shadow-pressed);}#mz-modal-input:focus{outline:none;box-shadow:var(--neu-shadow-pressed),0 0 0 3px rgba(74,111,165,0.2);}#mz-modal-buttons{display:flex;justify-content:flex-start;gap:12px;}.mz-modal-btn{display:inline-flex;align-items:center;justify-content:center;padding:10px 18px;font-family:"Space Grotesk",sans-serif;font-size:15px;font-weight:500;color:var(--text-color);background:var(--bg-color);border:none;border-radius:10px;cursor:pointer;transition:all 0.3s ease;min-width:90px;box-shadow:var(--neu-shadow-flat);}.mz-modal-btn:hover{box-shadow:var(--neu-shadow-pressed);transform:translateY(-1px);}.mz-modal-btn:active{box-shadow:var(--neu-shadow-pressed);transform:translateY(0);}.mz-modal-btn.primary{background:var(--bg-color);color:#70a9ff;font-weight:600;}.mz-modal-btn.primary:hover{color:#97c4ff;}.mz-modal-btn.cancel{background:var(--bg-color);color:#666;}.mz-modal-icon{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:50%;margin-right:14px;box-shadow:var(--neu-shadow-flat);}.mz-modal-icon.success{color:#22c55e;}.mz-modal-icon.error{color:#ef4444;}.mz-modal-icon.info{color:#4a6fa5;}.mz-modal-title-with-icon{display:flex;align-items:center;}.mz-modal-icon.success{color:#4ade80;}.mz-modal-icon.error{color:#f87171;}.mz-modal-icon.info{color:#60a5fa;}#mz_tactics_panel select{background-image:url("data:image/svg+xml;utf8,<svg fill='%23e0e5ec' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/></svg>");}.tactics-selector-container{position:relative;width:100%;}.tactics-dropdown-container{display:flex;flex-wrap:wrap;gap:10px;margin-top:10px;}.tactics-search-box{width:180px !important;padding:10px 12px;margin-bottom:0 !important;border:none;border-radius:10px;background-color:var(--bg-color);color:var(--text-color);font-family:"Space Grotesk",sans-serif;font-size:14px;box-shadow:var(--neu-shadow-pressed);box-sizing:border-box;height:40px;transition:all 0.3s ease,box-shadow 0.3s ease,transform 0.2s ease;position:relative;}.tactics-search-box:focus{outline:none;box-shadow:var(--neu-shadow-pressed),0 0 0 3px rgba(74,111,165,0.2);transform:translateY(-1px);}.tactics-search-box.filtering{border-bottom:2px solid var(--highlight-color);animation:pulse-border 1.5s infinite;}@keyframes pulse-border{0%{border-color:var(--highlight-color);}50%{border-color:transparent;}100%{border-color:var(--highlight-color);}}.tactics-filter-tabs{display:flex;width:100%;margin-top:10px;margin-bottom:10px;overflow-x:auto;padding-bottom:5px;}.tactics-filter-tab{padding:8px 14px;margin-right:8px;border:none;border-radius:8px;background-color:var(--bg-color);color:var(--text-color);font-family:"Space Grotesk",sans-serif;font-size:13px;cursor:pointer;white-space:nowrap;box-shadow:var(--neu-shadow-flat);transition:all 0.3s ease,transform 0.2s ease;}.tactics-filter-tab:hover{box-shadow:var(--neu-shadow-pressed);transform:translateY(-1px);}.tactics-filter-tab.active{box-shadow:var(--neu-shadow-pressed);font-weight:500;transform:translateY(1px);}.tactics-filter-tab[data-filter="short_passing"]{border-bottom:2px solid var(--short-passing-color);}.tactics-filter-tab[data-filter="wing_play"]{border-bottom:2px solid var(--wing-play-color);}.tactics-filter-tab[data-filter="other"]{border-bottom:2px solid var(--other-style-color);}.tactics-dropdown-wrapper{flex:1;min-width:200px;position:relative;}.tactics-style-indicator{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px;}.tactics-style-indicator.short_passing{background-color:var(--short-passing-color);}.tactics-style-indicator.wing_play{background-color:var(--wing-play-color);}.tactics-style-indicator.other{background-color:var(--other-style-color);}.tactics-style-indicator.uncategorized{background-color:var(--uncategorized-color);}.tactics-category-header{color:rgba(224,229,236,0.7);font-size:12px;font-weight:600;padding:4px 10px;background:rgba(0,0,0,0.2);margin-top:4px;border-radius:4px;}.tactics-selector-modal{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);backdrop-filter:blur(3px);display:flex;justify-content:center;align-items:center;z-index:10000;opacity:0;visibility:hidden;transition:opacity 0.3s ease;}.tactics-selector-modal.active{opacity:1;visibility:visible;}.tactics-modal-content{width:90%;max-width:500px;max-height:80vh;background:var(--bg-color);border-radius:var(--border-radius);box-shadow:var(--neu-shadow-flat);overflow:hidden;transform:scale(0.9);transition:transform 0.3s ease;}.tactics-selector-modal.active .tactics-modal-content{transform:scale(1);}.tactics-modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid rgba(51,51,51,0.2);}.tactics-modal-title{font-size:18px;font-weight:500;color:var(--text-color);}.tactics-modal-close{width:32px;height:32px;border-radius:50%;background:var(--bg-color);color:var(--text-color);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:var(--neu-shadow-flat);transition:all 0.3s ease;}.tactics-modal-close:hover{box-shadow:var(--neu-shadow-pressed);}.tactics-modal-search{padding:16px 20px;border-bottom:1px solid rgba(51,51,51,0.2);}.tactics-modal-filters{padding:12px 20px;border-bottom:1px solid rgba(51,51,51,0.2);display:flex;flex-wrap:wrap;gap:8px;}.tactics-modal-list{padding:16px 20px;max-height:50vh;overflow-y:auto;}.tactics-modal-item{padding:10px 14px;margin-bottom:8px;border-radius:8px;background:var(--bg-color);color:var(--text-color);cursor:pointer;display:flex;align-items:center;box-shadow:var(--neu-shadow-flat);transition:all 0.3s ease;}.tactics-modal-item:hover{box-shadow:var(--neu-shadow-pressed);}.tactics-modal-item.selected{box-shadow:var(--neu-shadow-pressed);background:rgba(74,111,165,0.1);}.tactics-modal-actions{padding:16px 20px;border-top:1px solid rgba(51,51,51,0.2);display:flex;justify-content:flex-end;gap:10px;}#category-selector{width:100%;margin-top:10px;padding:10px 12px;border:none;border-radius:10px;background-color:var(--bg-color);color:var(--text-color);font-family:"Space Grotesk",sans-serif;font-size:14px;box-shadow:var(--neu-shadow-pressed);box-sizing:border-box;}#category-selector option{padding:8px;}.category-selection-container{margin-top:15px;margin-bottom:5px;}.category-selection-label{display:block;margin-bottom:5px;font-size:14px;color:var(--text-color);opacity:0.9;}.mz-language-container{display:flex;align-items:center;gap:10px;}.mz-language-label{font-size:14px;font-weight:500;}.mz-language-dropdown{flex:1;}.new-category-input-container{margin-top:10px;display:none;}.new-category-input-container.visible{display:block;}#new-category-input{width:100%;padding:10px 12px;border:none;border-radius:10px;background-color:var(--bg-color);color:var(--text-color);font-family:"Space Grotesk",sans-serif;font-size:14px;box-shadow:var(--neu-shadow-pressed);box-sizing:border-box;}#new-category-input:focus{outline:none;box-shadow:var(--neu-shadow-pressed),0 0 0 3px rgba(74,111,165,0.2);}.filter-tab-custom{border-bottom:2px solid;}#tactics_selector{height:40px;box-sizing:border-box;max-height:300px;overflow-y:auto;}#tactics_selector option{animation:fadeIn 0.3s ease;background-color:var(--bg-color);padding:8px 12px;margin:2px 0;}#tactics_selector optgroup{background-color:rgba(79,124,172,0.2);border-left:3px solid var(--accent-color);font-weight:600;padding:8px 10px;margin-top:5px;border-radius:6px;color:var(--text-color);}@keyframes fadeIn{from{opacity:0;transform:translateY(-5px);}to{opacity:1;transform:translateY(0);}}@keyframes shake{0%,100%{transform:translateX(0);}25%{transform:translateX(-2px);}50%{transform:translateX(0);}75%{transform:translateX(2px);}}.tactics-dropdown-wrapper.filtering:after{content:'';position:absolute;width:10px;height:10px;border-radius:50%;background-color:var(--highlight-color);right:40px;top:15px;animation:pulse 1.5s infinite;}@keyframes pulse{0%{transform:scale(0.8);opacity:0.5;}50%{transform:scale(1.2);opacity:1;}100%{transform:scale(0.8);opacity:0.5;}}`);

    // ==============================
    // CONSTANTS AND VARIABLES
    // ==============================

    const OUTFIELD_PLAYERS_SELECTOR = ".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 GOALKEEPER_SELECTOR = ".fieldpos.fieldpos-ok.goalkeeper.ui-draggable";
    const FORMATION_TEXT_SELECTOR = "#formation_text";
    const TACTIC_SLOT_SELECTOR = ".ui-state-default.ui-corner-top.ui-tabs-selected.ui-state-active.invalid";
    const MIN_OUTFIELD_PLAYERS = 10;
    const MAX_TACTIC_NAME_LENGTH = 50;
    const 中国地区 = ['CN', 'HK', 'MO', 'TW'];
    const CDN_URLS = {
        default: {
            tactics: "https://u18mz.vercel.app/mz/userscript/tactics/json/defaultTactics.json",
            lang: "https://u18mz.vercel.app/mz/userscript/tactics/json/lang/"
        },
        china: {
            tactics: "https://pub-02de1c06eac643f992bb26daeae5c7a0.r2.dev/json/defaultTactics.json",
            lang: "https://pub-02de1c06eac643f992bb26daeae5c7a0.r2.dev/json/lang/"
        }
    };
    const BASE_FLAG_URL = "https://flagcdn.com/w320/";
    const LANGUAGES = [
        { code: "en", name: "English", flag: BASE_FLAG_URL + "gb.png" },
        { code: "pt", name: "Português", flag: BASE_FLAG_URL + "br.png" },
        { code: "zh", name: "中文", flag: BASE_FLAG_URL + "cn.png" },
        { code: "sv", name: "Svenska", flag: BASE_FLAG_URL + "se.png" },
        { code: "no", name: "Norsk", flag: BASE_FLAG_URL + "no.png" },
        { code: "da", name: "Dansk", flag: BASE_FLAG_URL + "dk.png" },
        { code: "es", name: "Español", flag: BASE_FLAG_URL + "ar.png" },
        { code: "pl", name: "Polski", flag: BASE_FLAG_URL + "pl.png" },
        { code: "nl", name: "Nederlands", flag: BASE_FLAG_URL + "nl.png" },
        { code: "id", name: "Bahasa Indonesia", flag: BASE_FLAG_URL + "id.png" },
        { code: "de", name: "Deutsch", flag: BASE_FLAG_URL + "de.png" },
        { code: "it", name: "Italiano", flag: BASE_FLAG_URL + "it.png" },
        { code: "fr", name: "Français", flag: BASE_FLAG_URL + "fr.png" },
        { code: "ro", name: "Română", flag: BASE_FLAG_URL + "ro.png" },
        { code: "tr", name: "Türkçe", flag: BASE_FLAG_URL + "tr.png" },
        { code: "ko", name: "한국어", flag: BASE_FLAG_URL + "kr.png" },
        { code: "ru", name: "Русский", flag: BASE_FLAG_URL + "ru.png" },
        { code: "ar", name: "العربية", flag: BASE_FLAG_URL + "sa.png" },
        { code: "jp", name: "日本語", flag: BASE_FLAG_URL + "jp.png" }
    ];
    const VERSION = "12.0.1";
    const VERSION_KEY = "mz_tactics_version";
    const CATEGORIES_STORAGE_KEY = "mz_tactics_categories";
    const TACTICS_STORAGE_KEY = "ls_tactics";
    const DEFAULT_CATEGORIES = {
        "short_passing": { id: "short_passing", name: "Short Passing", color: "#4682B4" },
        "wing_play": { id: "wing_play", name: "Wing Play", color: "#3CB371" }
    };
    const NEW_CATEGORY_ID = "new_category";
    const OTHER_CATEGORY_ID = "other";
    const USERSCRIPT_STRINGS = {
        addButton: "Add",
        addWithXmlButton: "Add with XML",
        deleteButton: "Delete",
        renameButton: "Edit",
        updateButton: "Update Coords",
        clearButton: "Clear All",
        resetButton: "Reset",
        importButton: "Import",
        exportButton: "Export",
        usefulLinksButton: "Links",
        aboutButton: "About",
        tacticNamePrompt: "Please enter a name for the tactic",
        addAlert: "Tactic {} added successfully.",
        deleteAlert: "Tactic {} deleted successfully.",
        renameAlert: "Tactic {} successfully edited.",
        updateAlert: "Tactic {} updated successfully.",
        clearAlert: "Tactics cleared successfully.",
        resetAlert: "Tactics reset successfully.",
        importAlert: "Tactics imported successfully.",
        exportAlert: "Tactics copied to clipboard.",
        deleteConfirmation: "Do you really want to delete {}?",
        updateConfirmation: "Do you really want to update {}?",
        clearConfirmation: "Do you really want to clear tactics?",
        resetConfirmation: "Do you really want to reset tactics?",
        invalidTacticError: "Invalid tactic.",
        noTacticNameProvidedError: "No tactic name provided.",
        alreadyExistingTacticNameError: "Tactic name already exists.",
        tacticNameMaxLengthError: "Tactic name is too long.",
        noTacticSelectedError: "No tactic selected.",
        duplicateTacticError: "Duplicate tactic.",
        noChangesMadeError: "No changes made.",
        invalidImportError: "Invalid import data.",
        modalContentInfoText: "This is the tactic selector.",
        modalContentFeedbackText: "Send your feedback.",
        usefulContent: "Some useful resources:",
        tacticsDropdownMenuLabel: "Select a tactic:",
        languageDropdownMenuLabel: "Language:",
        errorTitle: "Error",
        doneTitle: "Success",
        confirmationTitle: "Confirmation",
        deleteTacticConfirmButton: "Delete",
        cancelConfirmButton: "Cancel",
        updateConfirmButton: "Update",
        clearTacticsConfirmButton: "Clear",
        resetTacticsConfirmButton: "Reset",
        addConfirmButton: "Add",
        xmlValidationError: "Invalid XML.",
        xmlParsingError: "Error parsing XML.",
        xmlPlaceholder: "Paste XML here",
        tacticNamePlaceholder: "Tactic name",
        managerTitle: "MZ Tactics Manager",
        tacticActionsTitle: "Actions",
        otherActionsTitle: "Other",
        searchPlaceholder: "Search tactics",
        allTacticsFilter: "All",
        selectTacticButton: "Select",
        openTacticsSelector: "Browse Tactics",
        noTacticsFound: "No tactics found",
        welcomeMessage: "Welcome to MZ Tactics Manager v12!\n\nWhat's new:\n• Categories for organizing tactics (existing tactics categories can be edited)\n• Basic filtering\n• UI\n\nIf you have any questions or suggestions, feel free to message douglaskampl via chat or guestbook.",
        welcomeGotIt: "Got it!"
    };
    const ELEMENT_STRING_KEYS = {
        add_tactic_button: "addButton",
        add_tactic_with_xml_button: "addWithXmlButton",
        delete_tactic_button: "deleteButton",
        /* - - - - Temporarily disabled - - - - */
        /* 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"
    };
    const DEFAULT_MODAL_STRINGS = {
        ok: "OK",
        cancel: "Cancel",
        error: "Error",
        close: "×"
    };
    const region = isLikelyFromChina() ? 'china' : 'default';
    const defaultTacticsDataUrl = CDN_URLS[region].tactics;
    const langDataBaseUrl = CDN_URLS[region].lang;
    let dropdownMenuTactics = [];
    let activeLanguage;
    let infoModal;
    let usefulLinksModal;
    let currentFilter = "all";
    let searchTerm = "";
    let categories = {};

    // ==============================
    // CUSTOM ALERT SYSTEM
    // ==============================

    function createModalIcon(type) {
        if (!type) return null;

        const icon = document.createElement('div');
        icon.classList.add('mz-modal-icon');

        if (type === 'success') {
            icon.classList.add('success');
            icon.innerHTML = '✓';
        } else if (type === 'error') {
            icon.classList.add('error');
            icon.innerHTML = '✗';
        }

        return icon;
    }

    function validateModalInput(input, validator, errorContainerId) {
        if (!validator) return null;

        const validationError = validator(input.value);
        if (!validationError) return null;

        const errorText = document.createElement('div');
        errorText.style.color = '#ef4444';
        errorText.style.marginTop = '-10px';
        errorText.style.marginBottom = '10px';
        errorText.style.fontSize = '13px';
        errorText.textContent = validationError;

        const existingError = document.getElementById(errorContainerId);
        if (existingError) {
            existingError.remove();
        }

        errorText.id = errorContainerId;
        input.parentNode.insertBefore(errorText, input.nextSibling);
        return validationError;
    }

    function closeModal(overlay, callback) {
        overlay.classList.remove('active');
        setTimeout(() => {
            document.body.removeChild(overlay);
            if (callback) callback();
        }, 300);
    }

    function handleAlertConfirm(options, input, categorySelector, newCategoryInput, overlay, resolve) {
        if (options.input === 'text' && options.inputValidator) {
            const hasError = validateModalInput(input, options.inputValidator, 'mz-modal-error');
            if (hasError) return;
        }

        let categoryValue = null;
        let newCategoryName = null;

        if (categorySelector) {
            categoryValue = categorySelector.value;
            if (categoryValue === NEW_CATEGORY_ID && newCategoryInput) {
                newCategoryName = newCategoryInput.value.trim();
                if (!newCategoryName) {
                    const errorText = document.createElement('div');
                    errorText.style.color = '#ef4444';
                    errorText.style.marginTop = '-10px';
                    errorText.style.marginBottom = '10px';
                    errorText.style.fontSize = '13px';
                    errorText.textContent = "Category name cannot be empty";
                    errorText.id = 'new-category-error';

                    const existingError = document.getElementById('new-category-error');
                    if (existingError) {
                        existingError.remove();
                    }

                    newCategoryInput.parentNode.insertBefore(errorText, newCategoryInput.nextSibling);
                    return;
                }

                const existingCategory = Object.values(categories).find(
                    cat => cat.name.toLowerCase() === newCategoryName.toLowerCase()
                );

                if (existingCategory) {
                    const errorText = document.createElement('div');
                    errorText.style.color = '#ef4444';
                    errorText.style.marginTop = '-10px';
                    errorText.style.marginBottom = '10px';
                    errorText.style.fontSize = '13px';
                    errorText.textContent = "This category already exists";
                    errorText.id = 'new-category-error';

                    const existingError = document.getElementById('new-category-error');
                    if (existingError) {
                        existingError.remove();
                    }

                    newCategoryInput.parentNode.insertBefore(errorText, newCategoryInput.nextSibling);
                    return;
                }
            }
        }

        closeModal(overlay, () => {
            if (options.input === 'text') {
                const result = { value: input ? input.value : null, isConfirmed: true };
                if (categorySelector) {
                    if (categoryValue === NEW_CATEGORY_ID && newCategoryName) {
                        const newCategoryId = generateCategoryId(newCategoryName);
                        const newCategory = {
                            id: newCategoryId,
                            name: newCategoryName,
                            color: generateCategoryColor(newCategoryName)
                        };
                        result.category = newCategory;
                        addCategory(newCategory);
                    } else {
                        result.category = categories[categoryValue] || categories[Object.keys(categories)[0]];
                    }
                }
                resolve(result);
            } else {
                resolve({ isConfirmed: true });
            }
        });
    }

    function handleAlertCancel(overlay, resolve) {
        closeModal(overlay, () => {
            resolve({ isConfirmed: false, value: null });
        });
    }

    function setUpKeyboardHandler(handleConfirm, handleCancel, input) {
        return function (e) {
            if (e.key === 'Escape') {
                handleCancel();
            } else if (e.key === 'Enter' && !(input && document.activeElement !== input)) {
                handleConfirm();
            }
        };
    }

    function showAlert(options) {
        return new Promise((resolve) => {
            const overlay = document.createElement('div');
            overlay.id = 'mz-modal-overlay';

            const container = document.createElement('div');
            container.id = 'mz-modal-container';

            const header = document.createElement('div');
            header.id = 'mz-modal-header';

            const titleContainer = document.createElement('div');
            titleContainer.classList.add('mz-modal-title-with-icon');

            const icon = createModalIcon(options.type);
            if (icon) titleContainer.appendChild(icon);

            const title = document.createElement('h2');
            title.id = 'mz-modal-title';
            title.textContent = options.title || '';
            titleContainer.appendChild(title);

            header.appendChild(titleContainer);

            const closeBtn = document.createElement('button');
            closeBtn.id = 'mz-modal-close';
            closeBtn.innerHTML = DEFAULT_MODAL_STRINGS.close;
            header.appendChild(closeBtn);

            const content = document.createElement('div');
            content.id = 'mz-modal-content';
            content.textContent = options.text || '';

            let input;
            let categorySelector;
            let newCategoryInput;

            if (options.input === 'text') {
                input = document.createElement('input');
                input.id = 'mz-modal-input';
                input.type = 'text';
                input.value = options.inputValue || '';
                input.placeholder = options.placeholder || '';
            }

            if (options.showCategorySelector) {
                const categoryContainer = document.createElement('div');
                categoryContainer.className = 'category-selection-container';

                const categoryLabel = document.createElement('label');
                categoryLabel.className = 'category-selection-label';
                categoryLabel.textContent = "Category:";
                categoryContainer.appendChild(categoryLabel);

                categorySelector = document.createElement('select');
                categorySelector.id = 'category-selector';

                const categoryList = Object.values(categories);
                categoryList.sort((a, b) => a.name.localeCompare(b.name));

                categoryList.forEach(category => {
                    if (category.id !== OTHER_CATEGORY_ID) {
                        const option = document.createElement('option');
                        option.value = category.id;
                        option.textContent = category.name;
                        categorySelector.appendChild(option);
                    }
                });

                const addNewOption = document.createElement('option');
                addNewOption.value = NEW_CATEGORY_ID;
                addNewOption.textContent = "New category";
                categorySelector.appendChild(addNewOption);

                if (options.currentCategory && options.currentCategory !== OTHER_CATEGORY_ID) {
                    categorySelector.value = options.currentCategory;
                }

                categorySelector.addEventListener('change', function() {
                    const newCategoryContainer = document.querySelector('.new-category-input-container');
                    if (this.value === NEW_CATEGORY_ID) {
                        newCategoryContainer.classList.add('visible');
                        newCategoryInput.focus();
                    } else {
                        newCategoryContainer.classList.remove('visible');
                    }
                });

                categoryContainer.appendChild(categorySelector);

                const newCategoryContainer = document.createElement('div');
                newCategoryContainer.className = 'new-category-input-container';

                newCategoryInput = document.createElement('input');
                newCategoryInput.id = 'new-category-input';
                newCategoryInput.type = 'text';
                newCategoryInput.placeholder = "Category";

                newCategoryContainer.appendChild(newCategoryInput);
                categoryContainer.appendChild(newCategoryContainer);

                if (content.textContent) {
                    content.appendChild(document.createElement('br'));
                }
                content.appendChild(categoryContainer);
            }

            const buttons = document.createElement('div');
            buttons.id = 'mz-modal-buttons';

            const confirmHandler = () => {
                handleAlertConfirm(options, input, categorySelector, newCategoryInput, overlay, resolve);
            };

            const cancelHandler = () => handleAlertCancel(overlay, resolve);

            const confirmBtn = document.createElement('button');
            confirmBtn.classList.add('mz-modal-btn', 'primary');
            confirmBtn.textContent = options.confirmButtonText || DEFAULT_MODAL_STRINGS.ok;
            confirmBtn.addEventListener('click', confirmHandler);
            buttons.appendChild(confirmBtn);

            if (options.showCancelButton) {
                const cancelBtn = document.createElement('button');
                cancelBtn.classList.add('mz-modal-btn', 'cancel');
                cancelBtn.textContent = options.cancelButtonText || DEFAULT_MODAL_STRINGS.cancel;
                cancelBtn.addEventListener('click', cancelHandler);
                buttons.appendChild(cancelBtn);
            }

            closeBtn.addEventListener('click', cancelHandler);

            const keydownHandler = setUpKeyboardHandler(confirmHandler, cancelHandler, input);
            document.addEventListener('keydown', keydownHandler);

            container.appendChild(header);
            container.appendChild(content);
            if (input) container.appendChild(input);
            container.appendChild(buttons);
            overlay.appendChild(container);

            document.body.appendChild(overlay);

            setTimeout(() => {
                overlay.classList.add('active');
                if (input) input.focus();
            }, 10);

            overlay.addEventListener('transitionend', () => {
                if (!overlay.classList.contains('active')) {
                    document.removeEventListener('keydown', keydownHandler);
                }
            });
        });
    }

    function showSuccessMessage(title, text) {
        return showAlert({
            title: title || USERSCRIPT_STRINGS.doneTitle,
            text: text,
            type: 'success'
        });
    }

    function showErrorMessage(title, text) {
        return showAlert({
            title: title || USERSCRIPT_STRINGS.errorTitle,
            text: text,
            type: 'error'
        });
    }

    function showWelcomeMessage() {
        return showAlert({
            title: "Welcome!",
            text: USERSCRIPT_STRINGS.welcomeMessage,
        });
    }

    // ==============================
    // UTILITY FUNCTIONS
    // ==============================

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

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

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

    async function fetchTacticsFromJson() {
        try {
            const response = await fetch(defaultTacticsDataUrl);
            if (!response.ok) {
                throw new Error('Primary URL failed');
            }
            return await response.json();
        } catch (error) {
            console.log('Primary tactics URL failed, trying fallback URL');
            const fallbackURL = (defaultTacticsDataUrl === CDN_URLS.default.tactics)
                ? CDN_URLS.china.tactics
                : CDN_URLS.default.tactics;

            const fallbackResponse = await fetch(fallbackURL);
            return await fallbackResponse.json();
        }
    }

    function storeTacticsInGMStorage(data) {
        GM_setValue(TACTICS_STORAGE_KEY, data);
    }

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

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

    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";
        }
    }

    function generateCategoryId(categoryName) {
        return sha256Hash(categoryName.toLowerCase()).substring(0, 10);
    }

    function generateCategoryColor(categoryName) {
        const hash = sha256Hash(categoryName);
        const hue = parseInt(hash.substring(0, 6), 16) % 360;
        const saturation = 60 + (parseInt(hash.substring(6, 8), 16) % 30);
        const lightness = 45 + (parseInt(hash.substring(8, 10), 16) % 15);
        return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
    }

    function addCategory(category) {
        categories[category.id] = category;
        saveCategories();
    }

    function saveCategories() {
        GM_setValue(CATEGORIES_STORAGE_KEY, categories);
    }

    function loadCategories() {
        const storedCategories = GM_getValue(CATEGORIES_STORAGE_KEY);
        if (storedCategories) {
            categories = storedCategories;
        } else {
            categories = { ...DEFAULT_CATEGORIES };
            saveCategories();
        }
    }

    function loadCategoryColor(categoryId) {
        if (categories[categoryId]) {
            return categories[categoryId].color;
        } else if (categoryId === 'short_passing') {
            return '#4682B4';
        } else if (categoryId === 'wing_play') {
            return '#3CB371';
        } else if (categoryId === 'other' || !categoryId) {
            return '#9370DB';
        } else {
            return '#888888';
        }
    }

    function getCategoryName(categoryId) {
        if (categories[categoryId]) {
            return categories[categoryId].name;
        } else if (categoryId === 'short_passing') {
            return 'Short Passing';
        } else if (categoryId === 'wing_play') {
            return 'Wing Play';
        } else if (categoryId === 'other' || !categoryId) {
            return 'Other';
        } else {
            return categoryId || 'Uncategorized';
        }
    }

    // ==============================
    // TACTICS MANAGEMENT FUNCTIONS
    // ==============================

    function handleTacticsSelection(tactic) {
        const outfieldPlayers = Array.from(document.querySelectorAll(OUTFIELD_PLAYERS_SELECTOR));
        const selectedTactic = dropdownMenuTactics.find((tacticData) => tacticData.name === tactic);
        if (selectedTactic) {
            if (outfieldPlayers.length < MIN_OUTFIELD_PLAYERS) {
                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(OUTFIELD_PLAYERS_SELECTOR));
        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(TACTIC_SLOT_SELECTOR);
        if (slot) {
            slot.classList.remove("invalid");
        }
    }

    function updateFormationText(formation) {
        const formationTextElement = document.querySelector(FORMATION_TEXT_SELECTOR);
        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 };
    }

    function validateTacticPlayerCount(outfieldPlayers) {
        const isGoalkeeper = document.querySelector(GOALKEEPER_SELECTOR);
        outfieldPlayers = outfieldPlayers.filter((player) => !player.classList.contains("fieldpos-collision"));
        if (outfieldPlayers.length < MIN_OUTFIELD_PLAYERS || !isGoalkeeper) {
            showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.invalidTacticError);
            return false;
        }
        return true;
    }

    // ==============================
    // TACTIC CRUD OPERATIONS
    // ==============================

    async function addNewTactic() {
        const outfieldPlayers = Array.from(document.querySelectorAll(OUTFIELD_PLAYERS_SELECTOR));
        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) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.duplicateTacticError);
            return;
        }

        const result = await showAlert({
            title: USERSCRIPT_STRINGS.tacticNamePrompt,
            input: 'text',
            inputValue: '',
            placeholder: USERSCRIPT_STRINGS.tacticNamePlaceholder,
            inputValidator: (value) => {
                if (!value) {
                    return USERSCRIPT_STRINGS.noTacticNameProvidedError;
                }
                if (value.length > MAX_TACTIC_NAME_LENGTH) {
                    return USERSCRIPT_STRINGS.tacticNameMaxLengthError;
                }
                if (dropdownMenuTactics.some((t) => t.name === value)) {
                    return USERSCRIPT_STRINGS.alreadyExistingTacticNameError;
                }
            },
            showCategorySelector: true,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.addConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });

        if (!result.isConfirmed || !result.value) {
            return;
        }

        const tacticName = result.value;
        const tacticCategory = result.category.id;

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

        await saveTacticToStorage(tactic);
        dropdownMenuTactics.push(tactic);
        updateTacticsDropdown();
        updateFilterTabs();

        const tacticsSelector = document.getElementById("tactics_selector");
        tacticsSelector.value = tactic.name;
        handleTacticsSelection(tactic.name);

        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.addAlert.replace("{}", tactic.name));
    }

    async function addNewTacticWithXml() {
        const xmlResult = await showAlert({
            title: USERSCRIPT_STRINGS.xmlPlaceholder,
            input: 'text',
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.addConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });

        if (!xmlResult.isConfirmed || !xmlResult.value) {
            return;
        }

        const xml = xmlResult.value;

        const nameResult = await showAlert({
            title: USERSCRIPT_STRINGS.tacticNamePrompt,
            input: 'text',
            placeholder: USERSCRIPT_STRINGS.tacticNamePlaceholder,
            inputValidator: (value) => {
                if (!value) {
                    return USERSCRIPT_STRINGS.noTacticNameProvidedError;
                }
                if (value.length > MAX_TACTIC_NAME_LENGTH) {
                    return USERSCRIPT_STRINGS.tacticNameMaxLengthError;
                }
                if (dropdownMenuTactics.some((t) => t.name === value)) {
                    return USERSCRIPT_STRINGS.alreadyExistingTacticNameError;
                }
            },
            showCategorySelector: true,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.addConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });

        if (!nameResult.isConfirmed || !nameResult.value) {
            return;
        }

        const name = nameResult.value;
        const category = nameResult.category.id;

        try {
            const newTactic = await convertXmlToTacticJson(xml, name);
            newTactic.style = category;

            const tacticId = generateUniqueId(newTactic.coordinates);
            const isDuplicate = await validateDuplicateTactic(tacticId);
            if (isDuplicate) {
                await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.duplicateTacticError);
                return;
            }
            newTactic.id = tacticId;
            await saveTacticToStorage(newTactic);
            dropdownMenuTactics.push(newTactic);
            updateTacticsDropdown();
            updateFilterTabs();

            const tacticsSelector = document.getElementById("tactics_selector");
            tacticsSelector.value = newTactic.name;
            handleTacticsSelection(newTactic.name);

            await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.addAlert.replace('{}', newTactic.name));
        } catch (e) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.xmlParsingError);
        }
    }

    async function deleteTactic() {
        const tacticsSelector = document.getElementById("tactics_selector");
        const selectedTactic = dropdownMenuTactics.find((tactic) => tactic.name === tacticsSelector.value);
        if (!selectedTactic) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
            return;
        }
        const confirmResult = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle,
            text: USERSCRIPT_STRINGS.deleteConfirmation.replace("{}", selectedTactic.name),
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.deleteTacticConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!confirmResult.isConfirmed) {
            return;
        }

        const deletedCategoryId = selectedTactic.style;
        const tacticsData = (await GM_getValue(TACTICS_STORAGE_KEY)) || { tactics: [] };
        tacticsData.tactics = tacticsData.tactics.filter((tactic) => tactic.id !== selectedTactic.id);
        await GM_setValue(TACTICS_STORAGE_KEY, tacticsData);
        dropdownMenuTactics = dropdownMenuTactics.filter((tactic) => tactic.id !== selectedTactic.id);

        if (currentFilter !== 'all' && currentFilter === deletedCategoryId) {
            const categoryStillHasTactics = dropdownMenuTactics.some(tactic => tactic.style === deletedCategoryId);
            if (!categoryStillHasTactics) {
                currentFilter = 'all';
            }
        }

        updateTacticsDropdown();
        updateFilterTabs();

        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.deleteAlert.replace("{}", selectedTactic.name));
    }

    async function renameTactic() {
        const tacticsSelector = document.getElementById("tactics_selector");
        const selectedTactic = dropdownMenuTactics.find((tactic) => tactic.name === tacticsSelector.value);
        if (!selectedTactic) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
            return;
        }
        const oldName = selectedTactic.name;
        const oldCategory = selectedTactic.style;

        const result = await showAlert({
            title: USERSCRIPT_STRINGS.tacticNamePrompt,
            input: 'text',
            inputValue: oldName,
            placeholder: USERSCRIPT_STRINGS.tacticNamePlaceholder,
            inputValidator: (value) => {
                if (!value) {
                    return USERSCRIPT_STRINGS.noTacticNameProvidedError;
                }
                if (value.length > MAX_TACTIC_NAME_LENGTH) {
                    return USERSCRIPT_STRINGS.tacticNameMaxLengthError;
                }
                if (value !== oldName && dropdownMenuTactics.some((t) => t.name === value)) {
                    return USERSCRIPT_STRINGS.alreadyExistingTacticNameError;
                }
            },
            showCategorySelector: true,
            currentCategory: selectedTactic.style === OTHER_CATEGORY_ID ? null : selectedTactic.style,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.updateConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });

        if (!result.isConfirmed || !result.value) {
            return;
        }

        const newName = result.value;
        const newCategory = result.category.id;

        const categoryChanged = oldCategory !== newCategory;

        const tacticsData = (await GM_getValue(TACTICS_STORAGE_KEY)) || { tactics: [] };
        tacticsData.tactics = tacticsData.tactics.map((tactic) => {
            if (tactic.id === selectedTactic.id) {
                tactic.name = newName;
                tactic.style = newCategory;
            }
            return tactic;
        });
        await GM_setValue(TACTICS_STORAGE_KEY, tacticsData);

        if (categoryChanged && currentFilter === oldCategory) {
            const oldCategoryStillHasTactics = tacticsData.tactics.some(tactic => tactic.style === oldCategory);
            if (!oldCategoryStillHasTactics) {
                currentFilter = 'all';
            }
        }

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

        updateTacticsDropdown();
        updateFilterTabs();
        tacticsSelector.value = newName;

        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, 'Changes applied!');
    }

    async function updateTactic() {
        const outfieldPlayers = Array.from(document.querySelectorAll(OUTFIELD_PLAYERS_SELECTOR));
        const tacticsSelector = document.getElementById("tactics_selector");
        const selectedTactic = dropdownMenuTactics.find((tactic) => tactic.name === tacticsSelector.value);
        if (!selectedTactic) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_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(TACTICS_STORAGE_KEY)) || { tactics: [] };
        const validationOutcome = await validateDuplicateTacticWithUpdatedCoord(newId, selectedTactic, tacticsData);
        if (validationOutcome === "unchanged") {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noChangesMadeError);
            return;
        } else if (validationOutcome === "duplicate") {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.duplicateTacticError);
            return;
        }
        const result = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle,
            text: USERSCRIPT_STRINGS.updateConfirmation.replace("{}", selectedTactic.name),
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.updateConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!result.isConfirmed) {
            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(TACTICS_STORAGE_KEY, tacticsData);
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.updateAlert.replace("{}", selectedTactic.name));
    }

    async function clearTactics() {
        const confirmResult = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle,
            text: USERSCRIPT_STRINGS.clearConfirmation,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.clearTacticsConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!confirmResult.isConfirmed) {
            return;
        }
        await GM_deleteValue(TACTICS_STORAGE_KEY);
        dropdownMenuTactics = [];
        currentFilter = 'all';

        updateTacticsDropdown();
        updateFilterTabs();

        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.clearAlert);
    }

    async function resetTactics() {
        const confirmResult = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle,
            text: USERSCRIPT_STRINGS.resetConfirmation,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.resetTacticsConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!confirmResult.isConfirmed) {
            return;
        }

        await GM_deleteValue(TACTICS_STORAGE_KEY);
        currentFilter = 'all';

        try {
            const response = await fetch(defaultTacticsDataUrl);
            if (!response.ok) {
                throw new Error('Primary tactics URL failed');
            }
            const data = await response.json();
            const defaultTactics = data.tactics;

            defaultTactics.forEach(tactic => {
                if (!tactic.hasOwnProperty('style')) {
                    tactic.style = OTHER_CATEGORY_ID;
                }
            });

            await GM_setValue(TACTICS_STORAGE_KEY, { tactics: defaultTactics });
            dropdownMenuTactics = defaultTactics;
        } catch (error) {
            console.log('Primary tactics URL failed, trying fallback URL');
            const fallbackURL = (defaultTacticsDataUrl === CDN_URLS.default.tactics)
                ? CDN_URLS.china.tactics
                : CDN_URLS.default.tactics;

            const fallbackResponse = await fetch(fallbackURL);
            const fallbackData = await fallbackResponse.json();
            const defaultTactics = fallbackData.tactics;

            defaultTactics.forEach(tactic => {
                if (!tactic.hasOwnProperty('style')) {
                    tactic.style = OTHER_CATEGORY_ID;
                }
            });

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

        updateTacticsDropdown();
        updateFilterTabs();

        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.resetAlert);
    }

    // ==============================
    // IMPORT/EXPORT
    // ==============================

    async function importTactics() {
        try {
            const result = await showAlert({
                title: 'Import Tactics',
                input: 'text',
                inputValue: '',
                placeholder: 'Tactics JSON',
                showCancelButton: true,
                confirmButtonText: USERSCRIPT_STRINGS.importButton,
                cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
            });

            if (!result.isConfirmed || !result.value) {
                return;
            }

            let importedData;
            try {
                importedData = JSON.parse(result.value);
            } catch (e) {
                await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.invalidImportError);
                return;
            }

            if (!importedData || !Array.isArray(importedData.tactics)) {
                await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.invalidImportError);
                return;
            }

            const importedTactics = importedData.tactics;

            importedTactics.forEach(tactic => {
                if (!tactic.hasOwnProperty('style')) {
                    tactic.style = OTHER_CATEGORY_ID;
                }
            });

            let existingTactics = await GM_getValue(TACTICS_STORAGE_KEY, { 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(TACTICS_STORAGE_KEY, { tactics: mergedTactics });
            mergedTactics.sort((a, b) => a.name.localeCompare(b.name));
            dropdownMenuTactics = mergedTactics;

            updateTacticsDropdown();
            updateFilterTabs();

            await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.importAlert);
        } catch (error) {
            console.error(error);
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, DEFAULT_MODAL_STRINGS.error);
        }
    }

    async function exportTactics() {
        try {
            const tactics = GM_getValue(TACTICS_STORAGE_KEY, { tactics: [] });
            const tacticsJson = JSON.stringify(tactics, null, 2);

            if (navigator.clipboard?.writeText) {
                try {
                    await navigator.clipboard.writeText(tacticsJson);
                    await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.exportAlert);
                    return;
                } catch (clipboardError) {
                    console.warn(DEFAULT_MODAL_STRINGS.error, clipboardError);
                }
            }

            await showAlert({
                title: "Copy to Clipboard",
                text: "Please copy this JSON data manually:",
                input: 'text',
                inputValue: tacticsJson,
                confirmButtonText: "Done"
            });

        } catch (error) {
            console.error(error);
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, DEFAULT_MODAL_STRINGS.error);
        }
    }

    // ==============================
    // XML HANDLING
    // ==============================

    async function convertXmlToTacticJson(xmlString, tacticName) {
        const parser = new DOMParser();
        const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
        const parserError = xmlDoc.getElementsByTagName('parsererror');
        if (parserError.length > 0) {
            throw new Error('Invalid XML');
        }
        const posElements = Array.from(xmlDoc.getElementsByTagName('Pos'));
        const normalPosElements = posElements.filter(el => el.getAttribute('pos') === 'normal');
        const coordinates = normalPosElements.map(el => {
            const x = parseInt(el.getAttribute('x'));
            const y = parseInt(el.getAttribute('y'));
            const htmlLeft = x - 7;
            const htmlTop = y - 9;
            return [htmlLeft, htmlTop];
        });
        return {
            name: tacticName,
            coordinates: coordinates
        };
    }

    // ==============================
    // ENHANCED TACTICS SELECTOR
    // ==============================

    function createTacticsSelector() {
        const container = document.createElement('div');
        container.className = 'tactics-selector-section';

        const label = document.createElement('label');
        label.id = 'tactics_dropdown_menu_label';
        label.className = 'tactics-selector-label';
        label.textContent = USERSCRIPT_STRINGS.tacticsDropdownMenuLabel;
        container.appendChild(label);

        const dropdownContainer = document.createElement('div');
        dropdownContainer.className = 'tactics-dropdown-container';

        const dropdownWrapper = document.createElement('div');
        dropdownWrapper.className = 'tactics-dropdown-wrapper';

        const dropdown = document.createElement('select');
        dropdown.id = 'tactics_selector';

        dropdown.addEventListener('change', function() {
            handleTacticsSelection(this.value);
        });

        dropdownWrapper.appendChild(dropdown);
        dropdownContainer.appendChild(dropdownWrapper);

        const searchBox = document.createElement('input');
        searchBox.type = 'text';
        searchBox.className = 'tactics-search-box';
        searchBox.placeholder = "Search…"
        searchBox.addEventListener('input', (e) => {
            searchTerm = e.target.value.toLowerCase();
            updateTacticsDropdown();
        });
        dropdownContainer.appendChild(searchBox);

        const filterTabs = document.createElement('div');
        filterTabs.className = 'tactics-filter-tabs';
        filterTabs.id = 'tactics-filter-tabs';

        const allFilter = createFilterTab('all', "All", true);
        filterTabs.appendChild(allFilter);

        dropdownContainer.appendChild(filterTabs);
        container.appendChild(dropdownContainer);

        return container;
    }

    function createFilterTab(filter, label, isActive = false) {
        const tab = document.createElement('button');
        tab.className = 'tactics-filter-tab';
        if (isActive) tab.classList.add('active');
        tab.textContent = label;
        tab.dataset.filter = filter;

        if (filter !== 'all') {
            if (filter === OTHER_CATEGORY_ID) {
                tab.classList.add('filter-tab-custom');
                tab.style.borderBottomColor = '#9370DB';
            } else if (filter in categories) {
                tab.classList.add('filter-tab-custom');
                tab.style.borderBottomColor = categories[filter].color;
            }
        }

        tab.addEventListener('click', () => {
            document.querySelectorAll('.tactics-filter-tab').forEach(t => t.classList.remove('active'));
            tab.classList.add('active');
            currentFilter = filter;
            updateTacticsDropdown();
        });

        return tab;
    }

    function updateFilterTabs() {
        const filterTabsContainer = document.getElementById('tactics-filter-tabs');
        if (!filterTabsContainer) return;

        filterTabsContainer.innerHTML = '';

        const allFilter = createFilterTab('all', "All", currentFilter === 'all');
        filterTabsContainer.appendChild(allFilter);

        const usedCategories = new Set();
        dropdownMenuTactics.forEach(tactic => {
            if (tactic.style && tactic.style !== OTHER_CATEGORY_ID) {
                usedCategories.add(tactic.style);
            }
        });

        for (const categoryId of usedCategories) {
            if (categories[categoryId]) {
                const categoryFilter = createFilterTab(categoryId, categories[categoryId].name, currentFilter === categoryId);
                filterTabsContainer.appendChild(categoryFilter);
            }
        }

        if (currentFilter !== 'all' && currentFilter !== OTHER_CATEGORY_ID) {
            const categoryStillExists = usedCategories.has(currentFilter);
            if (!categoryStillExists) {
                currentFilter = 'all';
                document.querySelectorAll('.tactics-filter-tab').forEach(tab => {
                    tab.classList.remove('active');
                    if (tab.dataset.filter === 'all') {
                        tab.classList.add('active');
                    }
                });
            }
        }

        const hasUncategorizedTactics = dropdownMenuTactics.some(
            tactic => tactic.style === OTHER_CATEGORY_ID || !tactic.style
        );

        if (hasUncategorizedTactics) {
            const otherFilter = createFilterTab(OTHER_CATEGORY_ID, "Other", currentFilter === OTHER_CATEGORY_ID);
            filterTabsContainer.appendChild(otherFilter);
        } else if (currentFilter === OTHER_CATEGORY_ID) {
            currentFilter = 'all';
            document.querySelectorAll('.tactics-filter-tab').forEach(tab => {
                tab.classList.remove('active');
                if (tab.dataset.filter === 'all') {
                    tab.classList.add('active');
                }
            });
        }
    }

    function updateTacticsDropdown() {
        const dropdown = document.getElementById('tactics_selector');
        const dropdownWrapper = document.querySelector('.tactics-dropdown-wrapper');
        const searchBox = document.querySelector('.tactics-search-box');

        if (!dropdown) return;

        dropdown.innerHTML = '';

        if (searchTerm.length > 0) {
            dropdownWrapper.classList.add('filtering');
            searchBox.classList.add('filtering');
        } else {
            dropdownWrapper.classList.remove('filtering');
            searchBox.classList.remove('filtering');
        }

        const placeholderOption = document.createElement('option');
        placeholderOption.value = '';
        placeholderOption.textContent = '';
        placeholderOption.disabled = true;
        placeholderOption.selected = dropdownMenuTactics.length === 0;
        dropdown.appendChild(placeholderOption);

        const filteredTactics = dropdownMenuTactics.filter(tactic => {
            const matchesSearch = searchTerm === '' || tactic.name.toLowerCase().includes(searchTerm);
            const matchesFilter = currentFilter === 'all' ||
                  (currentFilter === OTHER_CATEGORY_ID && (tactic.style === OTHER_CATEGORY_ID || !tactic.style)) ||
                  tactic.style === currentFilter;
            return matchesSearch && matchesFilter;
        });

        const groupedTactics = {};

        for (const categoryId in categories) {
            groupedTactics[categoryId] = [];
        }

        if (!groupedTactics[OTHER_CATEGORY_ID]) {
            groupedTactics[OTHER_CATEGORY_ID] = [];
        }

        filteredTactics.forEach(tactic => {
            if (!tactic.style || tactic.style === OTHER_CATEGORY_ID) {
                groupedTactics[OTHER_CATEGORY_ID].push(tactic);
            } else {
                const categoryId = tactic.style;
                if (!groupedTactics[categoryId]) {
                    groupedTactics[categoryId] = [];
                }
                groupedTactics[categoryId].push(tactic);
            }
        });

        if (currentFilter === 'all') {
            for (const categoryId in groupedTactics) {
                if (groupedTactics[categoryId].length > 0) {
                    addTacticOptionsGroup(dropdown, groupedTactics[categoryId], getCategoryName(categoryId));
                }
            }
        } else if (currentFilter === OTHER_CATEGORY_ID) {
            if (groupedTactics[OTHER_CATEGORY_ID] && groupedTactics[OTHER_CATEGORY_ID].length > 0) {
                addTacticOptionsGroup(dropdown, groupedTactics[OTHER_CATEGORY_ID], "Other");
            }
        } else {
            if (groupedTactics[currentFilter] && groupedTactics[currentFilter].length > 0) {
                addTacticOptionsGroup(dropdown, groupedTactics[currentFilter], getCategoryName(currentFilter));
            }
        }

        if (filteredTactics.length === 0) {
            const noTacticsOption = document.createElement('option');
            noTacticsOption.disabled = true;
            noTacticsOption.textContent = USERSCRIPT_STRINGS.noTacticsFound;
            dropdown.appendChild(noTacticsOption);
        }

        dropdown.disabled = dropdownMenuTactics.length === 0;
    }

    function addTacticOptionsGroup(dropdown, tactics, groupLabel) {
        if (tactics.length === 0) return;

        const groupHeader = document.createElement('optgroup');
        groupHeader.label = groupLabel;
        dropdown.appendChild(groupHeader);

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

        tactics.forEach(tactic => {
            const option = document.createElement('option');
            option.value = tactic.name;
            option.dataset.style = tactic.style || OTHER_CATEGORY_ID;
            option.textContent = tactic.name;

            dropdown.appendChild(option);
        });
    }

    // ==============================
    // UI ELEMENT CREATION
    // ==============================

    function createButton(id, text, clickHandler) {
        const button = document.createElement("button");
        setUpButton(button, id, text);
        button.addEventListener("click", function () {
            clickHandler().catch((_) => { });
        });
        return button;
    }

    function createAddNewTacticButton() {
        return createButton("add_tactic_button", USERSCRIPT_STRINGS.addButton, addNewTactic);
    }

    function createAddNewTacticWithXmlButton() {
        return createButton("add_tactic_with_xml_button", USERSCRIPT_STRINGS.addWithXmlButton, addNewTacticWithXml);
    }

    function createDeleteTacticButton() {
        return createButton("delete_tactic_button", USERSCRIPT_STRINGS.deleteButton, deleteTactic);
    }

    function createRenameTacticButton() {
        return createButton("rename_tactic_button", 'Edit', renameTactic);
    }

    function createUpdateTacticButton() {
        return createButton("update_tactic_button", 'Update Coords', updateTactic);
    }

    function createClearTacticsButton() {
        return createButton("clear_tactics_button", USERSCRIPT_STRINGS.clearButton, clearTactics);
    }

    function createResetTacticsButton() {
        return createButton("reset_tactics_button", USERSCRIPT_STRINGS.resetButton, resetTactics);
    }

    function createImportTacticsButton() {
        return createButton("import_tactics_button", USERSCRIPT_STRINGS.importButton, importTactics);
    }

    function createExportTacticsButton() {
        return createButton("export_tactics_button", USERSCRIPT_STRINGS.exportButton, exportTactics);
    }

    // ==============================
    // VERSION HANDLING
    // ==============================

    async function checkVersion() {
        const storedVersion = GM_getValue(VERSION_KEY, null);
        if (!storedVersion || storedVersion !== VERSION) {
            await showWelcomeMessage();
            GM_setValue(VERSION_KEY, VERSION);
        }
    }

    // ==============================
    // AUDIO FEATURES
    // ==============================

    function 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;
    }

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

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

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

    function createAudioButton() {
        const button = document.createElement("button");
        setUpButton(button, "audio_button", "🔊");
        const audioUrls = [
            "https://ia801901.us.archive.org/31/items/corp.-palm-mall-01-palm-mall/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20Palm%20Mall%20-%2003%20Special%20Discount.mp3",
            "https://ia801901.us.archive.org/31/items/corp.-palm-mall-01-palm-mall/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20Palm%20Mall%20-%2004%20First%20Floor.mp3",
            "https://ia801901.us.archive.org/31/items/corp.-palm-mall-01-palm-mall/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20Palm%20Mall%20-%2006%20Second%20Floor.mp3",
            "https://ia801901.us.archive.org/7/items/palm-mall-mars-remastered/%E7%8C%AB%20%E3%82%B7%20Corp.%20%26%20SEPHORA%E8%84%B3%E3%83%90%E3%82%A4%E3%83%96%E3%82%B9%20-%20Palm%20Mall%20Mars%20%28remastered%29%20-%2006%20Second%20floor-%20%ED%99%98%EB%8C%80%20%26%20%EC%9D%8C%EC%95%85.mp3",
            "https://ia801901.us.archive.org/7/items/palm-mall-mars-remastered/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20Palm%20Mall%20Mars%20%28remastered%29%20-%2001%20%E3%82%B9%E3%82%AD%E3%83%9D%E3%83%BC%E3%83%AB%E7%A9%BA%E6%B8%AFPlaza.mp3",
            "https://ia801901.us.archive.org/7/items/palm-mall-mars-remastered/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20Palm%20Mall%20Mars%20%28remastered%29%20-%2009%20Sembikiya%20Restaurant.mp3",
            "https://ia804504.us.archive.org/20/items/5-wn9896/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20%E3%82%B7%E3%83%A7%E3%83%83%E3%83%97%20%40%20%E3%83%98%E3%83%AB%E3%82%B7%E3%83%B3%E3%82%AD%20-%2001%20FORUM%20%E6%B6%88%E8%B2%BB%E8%80%85-kuluttaja-.mp3",
            "https://ia904504.us.archive.org/20/items/5-wn9896/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20%E3%82%B7%E3%83%A7%E3%83%83%E3%83%97%20%40%20%E3%83%98%E3%83%AB%E3%82%B7%E3%83%B3%E3%82%AD%20-%2002%20Pelican%20Self%20Storage%20-Tilaa%20Kaikelle-.mp3",
            "https://ia904504.us.archive.org/20/items/5-wn9896/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20%E3%82%B7%E3%83%A7%E3%83%83%E3%83%97%20%40%20%E3%83%98%E3%83%AB%E3%82%B7%E3%83%B3%E3%82%AD%20-%2003%20%E8%B2%B7%E3%81%86%40JUMBO%20-Kauppakeskus-.mp3",
            "https://ia904504.us.archive.org/20/items/5-wn9896/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20%E3%82%B7%E3%83%A7%E3%83%83%E3%83%97%20%40%20%E3%83%98%E3%83%AB%E3%82%B7%E3%83%B3%E3%82%AD%20-%2005%20Hesburger%20%E6%98%A0%E7%94%BB%E9%A4%A8%20-hampurilainen-.mp3",
            "https://ia804504.us.archive.org/20/items/5-wn9896/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20%E3%82%B7%E3%83%A7%E3%83%83%E3%83%97%20%40%20%E3%83%98%E3%83%AB%E3%82%B7%E3%83%B3%E3%82%AD%20-%2006%20%E9%83%BD%E5%B8%82%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A9%E3%83%A0%20Consumer%20-kahvi-.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;
    }

    // ==============================
    // UI CONSTRUCTION
    // ==============================

    function createMainContainer() {
        const container = document.createElement("div");
        container.id = "mz_tactics_panel";
        container.classList.add("mz-panel");

        const tacticGroup = document.createElement("div");
        tacticGroup.classList.add("mz-group");

        const mainTitle = document.createElement("h2");
        mainTitle.classList.add("mz-group-main-title");
        const titleText = document.createElement("span");
        titleText.textContent = "MZ Tactics Manager";
        mainTitle.appendChild(titleText);
        const vText = document.createElement("span");
        vText.textContent = "v12";
        vText.classList.add("mz-version-text");
        mainTitle.appendChild(vText);

        const tacticsSelector = createTacticsSelector();

        const buttonsSection = document.createElement("div");
        buttonsSection.style.marginTop = "10px";
        const addNewTacticBtn = createAddNewTacticButton();
        const addNewTacticWithXmlBtn = createAddNewTacticWithXmlButton();
        const deleteTacticBtn = createDeleteTacticButton();
        const renameTacticBtn = createRenameTacticButton();
        const updateTacticBtn = createUpdateTacticButton();
        const clearTacticsBtn = createClearTacticsButton();
        const resetTacticsBtn = createResetTacticsButton();
        const importTacticsBtn = createImportTacticsButton();
        const exportTacticsBtn = createExportTacticsButton();

        appendChildren(buttonsSection, [
            addNewTacticBtn,
            addNewTacticWithXmlBtn,
            deleteTacticBtn,
            renameTacticBtn,
            updateTacticBtn,
            clearTacticsBtn,
            resetTacticsBtn,
            importTacticsBtn,
            exportTacticsBtn
        ]);

        appendChildren(tacticGroup, [
            mainTitle,
            tacticsSelector,
            buttonsSection,
            createHiddenTriggerButton()
        ]);

        const otherGroup = document.createElement("div");
        otherGroup.classList.add("mz-group");

        const otherContainer = document.createElement("div");
        otherContainer.style.display = "flex";
        otherContainer.style.justifyContent = "space-between";
        otherContainer.style.alignItems = "center";
        otherContainer.style.width = "100%";

        const otherLeftGroup = document.createElement("div");
        otherLeftGroup.style.display = "flex";
        otherLeftGroup.style.alignItems = "center";

        const usefulLinksBtn = createUsefulLinksButton();
        const aboutBtn = createAboutButton();
        const audioBtn = createAudioButton();
        appendChildren(otherLeftGroup, [usefulLinksBtn, aboutBtn, audioBtn]);

        const otherRightGroup = document.createElement("div");
        otherRightGroup.className = "mz-language-container";

        const languageLabel = document.createElement("div");
        languageLabel.id = "language_dropdown_menu_label";
        languageLabel.className = "mz-language-label";
        languageLabel.textContent = USERSCRIPT_STRINGS.languageDropdownMenuLabel;

        const languageDropdownWrapper = document.createElement("div");
        languageDropdownWrapper.className = "mz-language-dropdown";

        const languageDropdownMenu = createLanguageDropdownMenu();
        languageDropdownWrapper.appendChild(languageDropdownMenu);

        const flagImage = createFlagImage();

        appendChildren(otherRightGroup, [languageLabel, languageDropdownWrapper, flagImage]);
        appendChildren(otherContainer, [otherLeftGroup, otherRightGroup]);
        appendChildren(otherGroup, [otherContainer]);

        appendChildren(container, [tacticGroup, otherGroup]);

        return container;
    }

    function createHiddenTriggerButton() {
        const button = document.createElement("button");
        button.id = "hidden_trigger_button";
        button.textContent = "";
        button.style.visibility = "hidden";
        button.addEventListener("click", function () {
            const tacticsPresetInfo = {
                elem: document.getElementById("tactics_preset"),
                resetValue: "5-3-2"
            };
            tacticsPresetInfo.elem.value = tacticsPresetInfo.resetValue;
            tacticsPresetInfo.elem.dispatchEvent(new Event("change"));
        });
        return button;
    }

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

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

    function setUpButton(button, id, textContent) {
        button.id = id;
        button.classList.add('mzbtn');
        button.textContent = textContent;
    }

    function createLanguageDropdownMenu() {
        const dropdown = document.createElement("select");
        dropdown.id = "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((_) => { });
        });
        return dropdown;
    }

    function createFlagImage() {
        const img = document.createElement("img");
        img.id = "language_flag";
        const activeLang = LANGUAGES.find((lang) => lang.code === activeLanguage);
        if (activeLang) {
            img.src = activeLang.flag;
        }
        return img;
    }

    // ==============================
    // LOCALIZATION
    // ==============================

    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;
    }

    function updateTranslation() {
        for (const key in USERSCRIPT_STRINGS) {
            USERSCRIPT_STRINGS[key] = i18next.t(key);
        }
        for (const id in ELEMENT_STRING_KEYS) {
            const element = document.getElementById(id);
            if (id === "info_modal_info_text" || id === "info_modal_feedback_text") {
                if (element) element.innerHTML = USERSCRIPT_STRINGS[ELEMENT_STRING_KEYS[id]];
            } else if (element) {
                element.textContent = USERSCRIPT_STRINGS[ELEMENT_STRING_KEYS[id]];
            }
        }

        const allFilterTab = document.querySelector('.tactics-filter-tab[data-filter="all"]');
        if (allFilterTab) allFilterTab.textContent = "All";

        for (const categoryId in categories) {
            const filterTab = document.querySelector(`.tactics-filter-tab[data-filter="${categoryId}"]`);
            if (filterTab) filterTab.textContent = categories[categoryId].name;
        }

        const otherFilterTab = document.querySelector(`.tactics-filter-tab[data-filter="${OTHER_CATEGORY_ID}"]`);
        if (otherFilterTab) otherFilterTab.textContent = "Other";

        const searchBox = document.querySelector('.tactics-search-box');
        if (searchBox) searchBox.placeholder = "Search…";

        updateTacticsDropdown();
    }

    async function changeLanguage(languageCode) {
        try {
            const translationDataUrl = langDataBaseUrl + languageCode + ".json";
            let translations;

            try {
                const response = await fetch(translationDataUrl);
                if (!response.ok) {
                    throw new Error('Primary language URL failed');
                }
                translations = await response.json();
            } catch (error) {
                console.log('Primary language URL failed, trying fallback URL');
                const fallbackBaseUrl = (langDataBaseUrl === CDN_URLS.default.lang)
                    ? CDN_URLS.china.lang
                    : CDN_URLS.default.lang;

                const fallbackUrl = fallbackBaseUrl + languageCode + ".json";
                const fallbackResponse = await fetch(fallbackUrl);
                translations = await fallbackResponse.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");
                if (flagImage) flagImage.src = language.flag;
            }
        } catch (e) {
            console.error('Failed to change language:', e);
        }
    }

    // ==============================
    // UTILITY FUNCTIONS
    // ==============================

    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);
    }

    // ==============================
    // MODALS
    // ==============================

    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");
        const usefulContent = createUsefulContent();
        const resources = new Map([
            ["gewlaht - BoooM", "https://www.managerzone.com/?p=forum&sub=topic&topic_id=11415137&forum_id=49&sport=soccer"],
            ["taktikskola by honken91", "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"],
            ["Tactics guide by lukasz87o/filipek4", "https://www.managerzone.com/?p=forum&sub=topic&topic_id=12766444&forum_id=12&sport=soccer&share_sport=soccer"],
            ["MZExtension/van.mz.playerAdvanced by vanjoge", "https://greasyfork.org/pt-BR/scripts/373382-van-mz-playeradvanced"],
            ["Mazyar Userscript", "https://greasyfork.org/pt-BR/scripts/476290-mazyar"],
            ["Stats Xente Userscript", "https://greasyfork.org/pt-BR/scripts/491442-stats-xente-script"],
            ["More userscripts", "https://greasyfork.org/pt-BR/users/1088808-douglasdotv"]
        ]);
        const usefulLinksList = createLinksList(resources);
        modalContent.appendChild(usefulContent);
        modalContent.appendChild(usefulLinksList);
        return modalContent;
    }

    function createUsefulContent() {
        const usefulContent = document.createElement("p");
        usefulContent.id = "useful_content";
        usefulContent.textContent = USERSCRIPT_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);
    }

    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");
        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.style.fontSize = "24px";
        title.style.fontWeight = "bold";
        title.style.marginBottom = "20px";
        title.textContent = "MZ Tactics Manager";
        return title;
    }

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

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

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

    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 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 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 createUsefulLinksButton() {
        const button = document.createElement("button");
        setUpButton(button, "useful_links_button", USERSCRIPT_STRINGS.usefulLinksButton);
        button.addEventListener("click", function (event) {
            event.stopPropagation();
            toggleModal(usefulLinksModal);
        });
        return button;
    }

    function createAboutButton() {
        const button = document.createElement("button");
        setUpButton(button, "about_button", USERSCRIPT_STRINGS.aboutButton);
        button.addEventListener("click", function (event) {
            event.stopPropagation();
            toggleModal(infoModal);
        });
        return button;
    }

    function createToggleButton() {
        const button = document.createElement('button');
        button.id = 'toggle_panel_btn';
        button.innerHTML = 'X';
        button.title = 'Hide panel';
        return button;
    }

    function createCollapsedIcon() {
        const icon = document.createElement('div');
        icon.id = 'collapsed_icon';
        icon.innerHTML = 'MZTM';
        icon.title = 'Show MZ Tactics Manager';
        document.body.appendChild(icon);
        return icon;
    }

    // ==============================
    // REGION DETECTION
    // ==============================

    function isLikelyFromChina() {
        const lang = navigator.language || navigator.userLanguage || '';
        const ua = navigator.userAgent.toLowerCase();
        const region = navigator.language?.split('-')[1] || '';
        return lang.startsWith('zh-') ||
            ua.includes('micromessenger') ||
            ua.includes('qq') ||
            ua.includes('ucbrowser') ||
            中国地区.includes(region);
    }

    // ==============================
    // INITIALIZATION
    // ==============================

    function initializeLanguage() {
        return new Promise((resolve, reject) => {
            activeLanguage = getActiveLanguage();
            i18next.init({
                lng: activeLanguage,
                resources: {
                    [activeLanguage]: {
                        translation: {}
                    }
                }
            }).then(async () => {
                try {
                    let json;
                    try {
                        const url = langDataBaseUrl + activeLanguage + ".json";
                        const res = await fetch(url);
                        if (!res.ok) {
                            throw new Error('Primary language URL failed during initialization');
                        }
                        json = await res.json();
                    } catch (error) {
                        console.log('Primary language URL failed during initialization, trying fallback URL');
                        const fallbackBaseURL = (langDataBaseUrl === CDN_URLS.default.lang)
                            ? CDN_URLS.china.lang
                            : CDN_URLS.default.lang;

                        const fallbackUrl = fallbackBaseURL + activeLanguage + ".json";
                        const fallbackRes = await fetch(fallbackUrl);
                        json = await fallbackRes.json();
                    }

                    i18next.addResourceBundle(activeLanguage, "translation", json);
                    loadCategories();
                    await checkVersion();
                    resolve();
                } catch (error) {
                    reject(error);
                }
            }).catch(reject);
        });
    }

    function setUpTacticsInterface(mainContainer) {
        const mainTitle = mainContainer.querySelector('.mz-group-main-title');
        const toggleBtn = createToggleButton();
        const collapsedIcon = createCollapsedIcon();
        mainTitle.appendChild(toggleBtn);

        let isCollapsed = false;
        function togglePanel() {
            isCollapsed = !isCollapsed;
            mainContainer.classList.toggle('collapsed');
            toggleBtn.classList.toggle('collapsed');
            collapsedIcon.classList.toggle('visible');
        }

        toggleBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            togglePanel();
        });

        collapsedIcon.addEventListener('click', () => {
            togglePanel();
        });
    }

    async function loadTacticsData() {
        try {
            const data = await fetchTacticsFromGMStorage();
            dropdownMenuTactics = data.tactics;

            dropdownMenuTactics.forEach(tactic => {
                if (!tactic.hasOwnProperty('style')) {
                    tactic.style = OTHER_CATEGORY_ID;
                }
            });

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

            const tacticsSelector = document.getElementById("tactics_selector");
            if (tacticsSelector) {
                tacticsSelector.addEventListener("change", function () {
                    handleTacticsSelection(this.value);
                });
            }
        } catch (error) {
            console.error("Error loading tactics data:", error);
        }
    }

    function initialize() {
        const tacticsBox = document.getElementById("tactics_box");
        if (!tacticsBox) return;

        initializeLanguage()
            .then(() => {
                const mainContainer = createMainContainer();
                setUpTacticsInterface(mainContainer);

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

                setInfoModal();
                setUsefulLinksModal();
                setUpModalsWindowClickListener();
                updateTranslation();

                return loadTacticsData();
            })
            .catch(error => {
                console.error("Initialization error:", error);
            });
    }

    window.addEventListener("load", initialize);
})();