MZ Tactics Manager

Userscript to manage tactics in ManagerZone

目前為 2025-04-06 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name          MZ Tactics Manager
// @namespace     douglaskampl
// @version       13.0.0
// @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
// @grant         GM_getResourceText
// @require       https://cdnjs.cloudflare.com/ajax/libs/jsSHA/3.3.1/sha256.js
// @resource      mztmStyles https:/br18.org/mz/userscript/tactics/nakhon_ratchasima.css
// @run-at        document-idle
// @license       MIT
// ==/UserScript==

(function () {
    'use strict';

    GM_addStyle(GM_getResourceText('mztmStyles'));

    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_PLAYERS_ON_PITCH = 11;
    const MAX_TACTIC_NAME_LENGTH = 50;
    const SCRIPT_VERSION = '13.0.0';
    const DISPLAY_VERSION = '13';
    const SCRIPT_NAME = 'MZ Tactics Manager';
    const VERSION_KEY = 'mz_tactics_version';
    const COLLAPSED_KEY = 'mz_tactics_collapsed';
    const VIEW_MODE_KEY = 'mztm_view_mode';
    const CATEGORIES_STORAGE_KEY = 'mz_tactics_categories';
    const FORMATIONS_STORAGE_KEY = 'mztm_formations';
    const OLD_FORMATIONS_STORAGE_KEY = 'ls_tactics';
    const COMPLETE_TACTICS_STORAGE_KEY = 'mztm_complete_tactics';
    const ROSTER_CACHE_KEY = 'mztm_roster_cache';
    const USER_INFO_CACHE_KEY = 'mztm_user_info_cache';
    const ROSTER_CACHE_DURATION_MS = 3600000;
    const USER_INFO_CACHE_DURATION_MS = 86400000;
    const DEFAULT_CATEGORIES = {
        'short_passing': {
            id: 'short_passing',
            name: 'Short Passing',
            color: '#54a0ff'
        },
        'wing_play': {
            id: 'wing_play',
            name: 'Wing Play',
            color: '#5dd39e'
        }
    };
    const NEW_CATEGORY_ID = 'new_category';
    const OTHER_CATEGORY_ID = 'other';
    const USERSCRIPT_STRINGS = {
        addButton: 'Add',
        addCurrentTactic: 'Add Current',
        addWithXmlButton: 'Add via XML',
        manageButton: 'Manage',
        deleteButton: 'Delete',
        renameButton: 'Edit',
        updateButton: 'Save Positions',
        clearButton: 'Clear',
        resetButton: 'Reset',
        importButton: 'Import',
        exportButton: 'Export',
        infoButton: 'FAQ',
        tacticNamePrompt: 'Please enter a name for the tactic',
        addAlert: 'Tactic {} added successfully.',
        deleteAlert: 'Item {} deleted successfully.',
        renameAlert: 'Item {} successfully edited.',
        updateAlert: 'Tactic {} updated successfully.',
        clearAlert: 'Tactics cleared successfully.',
        resetAlert: 'Default tactics reset successfully.',
        importAlert: 'Tactics imported successfully.',
        exportAlert: 'Tactics JSON copied to clipboard.',
        deleteConfirmation: 'Do you really want to delete {}?',
        updateConfirmation: 'Do you really want to update {} coords?',
        clearConfirmation: 'Do you really want to clear all saved tactics?',
        resetConfirmation: 'Reset to default tactics? This will remove all your custom tactics.',
        invalidTacticError: 'Invalid tactic. Ensure 11 players are on the pitch.',
        noTacticNameProvidedError: 'No name provided.',
        alreadyExistingTacticNameError: 'Name already exists.',
        tacticNameMaxLengthError: `Name is too long (max ${MAX_TACTIC_NAME_LENGTH} chars).`,
        noTacticSelectedError: 'No item selected.',
        duplicateTacticError: 'This tactic already exists.',
        noChangesMadeError: 'No changes detected in player positions.',
        invalidImportError: 'Invalid import data. Please provide valid JSON.',
        modalContentInfoText: 'MZ Tactics Manager by douglaskampl.',
        modalContentFeedbackText: 'For feedback or suggestions, contact via GB/Chat.',
        usefulContent: '',
        tacticsDropdownMenuLabel: 'Select a Tactic',
        errorTitle: 'Error',
        doneTitle: 'Success',
        confirmationTitle: 'Confirmation',
        deleteTacticConfirmButton: 'Delete',
        cancelConfirmButton: 'Cancel',
        updateConfirmButton: 'Update',
        clearTacticsConfirmButton: 'Clear',
        resetTacticsConfirmButton: 'Reset',
        addConfirmButton: 'Add',
        xmlValidationError: 'Invalid XML format.',
        xmlParsingError: 'Error parsing XML.',
        xmlPlaceholder: 'Paste Tactic XML here',
        tacticNamePlaceholder: 'Tactic name',
        managerTitle: SCRIPT_NAME,
        searchPlaceholder: 'Search Tactics...',
        allTacticsFilter: 'All',
        noTacticsFound: 'No tactics found',
        welcomeMessage: `Welcome to ${SCRIPT_NAME} v${DISPLAY_VERSION}!\n\nNew features:\n• Save and load complete tactics (initial, alt1, alt2, playstyles and tactic rules).\n\nEnjoy!`,
        welcomeGotIt: 'Got it!',
        removeCategoryConfirmation: 'Remove category "{}"? (All tactics in this category will be moved to "Other").',
        removeCategoryAlert: 'Category "{}" removed successfully.',
        removeCategoryButton: 'Remove',
        completeTacticsTitle: 'Complete Tactics Management',
        saveCompleteTacticButton: 'Save Current',
        loadCompleteTacticButton: 'Load',
        deleteCompleteTacticButton: 'Delete',
        completeTacticNamePrompt: 'Please enter a name for the tactic',
        completeTacticSaveSuccess: 'Tactic {} saved successfully.',
        completeTacticLoadSuccess: 'Tactic {} loaded successfully.',
        completeTacticDeleteSuccess: 'Complete Tactic {} deleted successfully.',
        errorFetchingRoster: 'Error fetching team roster. Cannot load Complete Tactic.',
        errorInsufficientPlayers: 'Load failed: Not enough available players in roster to fill required positions.',
        errorXmlExportParse: 'Error parsing XML from native export.',
        errorXmlGenerate: 'Error generating XML for import.',
        errorImportFailed: 'Native import failed. Check XML validity or player availability.',
        warningPlayersSubstituted: 'Warning: roster mismatch. Some players substituted randomly. Stored tactic updated.',
        invalidXmlForImport: 'MZ rejected the generated XML. It might be invalid or player assignments failed.',
        completeTacticNamePlaceholder: 'Tactic name',
        normalModeLabel: 'Normal',
        completeModeLabel: 'Complete',
        modeLabel: ''
    };
    const DEFAULT_MODAL_STRINGS = {
        ok: 'OK',
        cancel: 'Cancel',
        error: 'Error',
        close: '×'
    };

    let tactics = [];
    let completeTactics = {};
    let currentFilter = 'all';
    let searchTerm = '';
    let categories = {};
    let rosterCache = {
        data: null,
        timestamp: 0,
        teamId: null
    };
    let userInfoCache = {
        teamId: null,
        username: null,
        timestamp: 0
    };
    let teamId = null;
    let username = null;
    let loadingOverlay = null;
    let currentViewMode = 'normal';
    let collapsedIconElement = null;

    function createModalIcon(type) {
        if (!type) return null;
        const i = document.createElement('div');
        i.classList.add('mz-modal-icon');
        if (type === 'success') {
            i.classList.add('success');
            i.innerHTML = '✓';
        } else if (type === 'error') {
            i.classList.add('error');
            i.innerHTML = '✗';
        } else if (type === 'info') {
            i.classList.add('info');
            i.innerHTML = 'ℹ';
        }
        return i;
    }

    function validateModalInput(inputElement, validatorFn, errorElementId) {
        if (!validatorFn || !inputElement || !inputElement.parentNode) return null;
        const validationError = validatorFn(inputElement.value);
        const existingError = document.getElementById(errorElementId);
        if (existingError) existingError.remove();
        if (!validationError) return null;
        const errorContainer = document.createElement('div');
        errorContainer.id = errorElementId;
        errorContainer.style.color = '#ff6b6b';
        errorContainer.style.marginTop = '-10px';
        errorContainer.style.marginBottom = '10px';
        errorContainer.style.fontSize = '13px';
        errorContainer.textContent = validationError;
        inputElement.parentNode.insertBefore(errorContainer, inputElement.nextSibling);
        return validationError;
    }

    function closeModal(overlayElement, callback) {
        if (!overlayElement) return;
        overlayElement.classList.remove('active');
        setTimeout(() => {
            if (overlayElement && overlayElement.parentNode === document.body) document.body.removeChild(overlayElement);
            if (callback) callback();
        }, 300);
    }

    function handleAlertConfirm(options, inputElement, categorySelect, newCategoryInput, overlayElement, resolve) {
        if (options.input === 'text' && options.inputValidator && inputElement) {
            const validationError = validateModalInput(inputElement, options.inputValidator, 'mz-modal-error');
            if (validationError) return;
        }
        let selectedCategoryId = null;
        let newCategoryName = null;
        if (categorySelect) {
            selectedCategoryId = categorySelect.value;
            if (selectedCategoryId === NEW_CATEGORY_ID && newCategoryInput) {
                newCategoryName = newCategoryInput.value.trim();
                const categoryErrorElement = document.getElementById('new-category-error');
                if (categoryErrorElement) categoryErrorElement.remove();
                if (!newCategoryName) {
                    const errorText = document.createElement('div');
                    errorText.style.color = '#ff6b6b';
                    errorText.style.marginTop = '5px';
                    errorText.style.fontSize = '13px';
                    errorText.textContent = 'Category name cannot be empty.';
                    errorText.id = 'new-category-error';
                    newCategoryInput.parentNode.appendChild(errorText);
                    return;
                }
                const existingCategory = Object.values(categories).find(cat => cat.name.toLowerCase() === newCategoryName.toLowerCase());
                if (existingCategory) {
                    const errorText = document.createElement('div');
                    errorText.style.color = '#ff6b6b';
                    errorText.style.marginTop = '5px';
                    errorText.style.fontSize = '13px';
                    errorText.textContent = 'Category name already exists.';
                    errorText.id = 'new-category-error';
                    newCategoryInput.parentNode.appendChild(errorText);
                    return;
                }
            }
        }
        closeModal(overlayElement, () => {
            let result = {
                isConfirmed: true
            };
            if (options.input === 'text') {
                result.value = inputElement ? inputElement.value : null;
            }
            if (categorySelect) {
                if (selectedCategoryId === 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[selectedCategoryId] || categories[OTHER_CATEGORY_ID] || {
                        id: OTHER_CATEGORY_ID,
                        name: 'Other',
                        color: '#8395a7'
                    };
                }
            }
            resolve(result);
        });
    }

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

    function setUpKeyboardHandler(confirmHandler, cancelHandler, inputElement) {
        return function(event) {
            if (event.key === 'Escape') {
                cancelHandler();
            } else if (event.key === 'Enter' && !(inputElement && document.activeElement === inputElement && inputElement.tagName === 'TEXTAREA')) {
                confirmHandler();
            }
        };
    }

    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 closeButton = document.createElement('button');
            closeButton.id = 'mz-modal-close';
            closeButton.innerHTML = DEFAULT_MODAL_STRINGS.close;
            header.appendChild(closeButton);
            const content = document.createElement('div');
            content.id = 'mz-modal-content';
            if (options.htmlContent) {
                content.appendChild(options.htmlContent);
            } else if (options.text) {
                const textNode = document.createTextNode(options.text);
                content.appendChild(textNode);
            }
            let inputElem = null,
                categorySelectElem = null,
                newCategoryInputElem = null,
                categoryContainer = null;
            if (options.input === 'text') {
                inputElem = document.createElement('input');
                inputElem.id = 'mz-modal-input';
                inputElem.type = 'text';
                inputElem.value = options.inputValue || '';
                inputElem.placeholder = options.placeholder || '';
            }
            if (options.showCategorySelector) {
                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);
                categorySelectElem = document.createElement('select');
                categorySelectElem.id = 'category-selector';
                const usedCategoryIds = new Set(tactics.map(t => t.style).filter(Boolean));
                if (options.currentCategory) usedCategoryIds.add(options.currentCategory);
                const availableCategories = Object.values(categories).filter(cat => DEFAULT_CATEGORIES[cat.id] || cat.id === OTHER_CATEGORY_ID || usedCategoryIds.has(cat.id));
                availableCategories.sort((a, b) => {
                    if (a.id === OTHER_CATEGORY_ID) return 1;
                    if (b.id === OTHER_CATEGORY_ID) return -1;
                    return a.name.localeCompare(b.name);
                });
                availableCategories.forEach(cat => {
                    if (cat.id !== OTHER_CATEGORY_ID) {
                        const opt = document.createElement('option');
                        opt.value = cat.id;
                        opt.textContent = cat.name;
                        categorySelectElem.appendChild(opt);
                    }
                });
                const otherOption = document.createElement('option');
                otherOption.value = OTHER_CATEGORY_ID;
                otherOption.textContent = getCategoryName(OTHER_CATEGORY_ID);
                categorySelectElem.appendChild(otherOption);
                const addNewOption = document.createElement('option');
                addNewOption.value = NEW_CATEGORY_ID;
                addNewOption.textContent = '+ New category';
                categorySelectElem.appendChild(addNewOption);
                categorySelectElem.value = (options.currentCategory && categories[options.currentCategory]) ? options.currentCategory : OTHER_CATEGORY_ID;
                const newCategoryContainer = document.createElement('div');
                newCategoryContainer.className = 'new-category-input-container';
                newCategoryInputElem = document.createElement('input');
                newCategoryInputElem.id = 'new-category-input';
                newCategoryInputElem.type = 'text';
                newCategoryInputElem.placeholder = 'New category name';
                newCategoryContainer.appendChild(newCategoryInputElem);
                categorySelectElem.addEventListener('change', function() {
                    const isNew = this.value === NEW_CATEGORY_ID;
                    newCategoryContainer.classList.toggle('visible', isNew);
                    if (isNew) newCategoryInputElem.focus();
                    const categoryError = document.getElementById('new-category-error');
                    if (categoryError) categoryError.remove();
                });
                categoryContainer.appendChild(categorySelectElem);
                categoryContainer.appendChild(newCategoryContainer);
            }
            const buttons = document.createElement('div');
            buttons.id = 'mz-modal-buttons';
            const confirmHandler = () => handleAlertConfirm(options, inputElem, categorySelectElem, newCategoryInputElem, overlay, resolve);
            const cancelHandler = () => handleAlertCancel(overlay, resolve);
            const confirmButton = document.createElement('button');
            confirmButton.classList.add('mz-modal-btn', 'primary');
            confirmButton.textContent = options.confirmButtonText || DEFAULT_MODAL_STRINGS.ok;
            confirmButton.addEventListener('click', confirmHandler);
            buttons.appendChild(confirmButton);
            if (options.showCancelButton) {
                const cancelButton = document.createElement('button');
                cancelButton.classList.add('mz-modal-btn', 'cancel');
                cancelButton.textContent = options.cancelButtonText || DEFAULT_MODAL_STRINGS.cancel;
                cancelButton.addEventListener('click', cancelHandler);
                buttons.appendChild(cancelButton);
            }
            closeButton.addEventListener('click', cancelHandler);
            const keyboardHandler = setUpKeyboardHandler(confirmHandler, cancelHandler, inputElem);
            document.addEventListener('keydown', keyboardHandler);

            container.appendChild(header);
            container.appendChild(content);
            if (inputElem) {
                container.appendChild(inputElem);
            }
            if (categoryContainer) {
                container.appendChild(categoryContainer);
            }
            container.appendChild(buttons);

            overlay.appendChild(container);
            document.body.appendChild(overlay);
            setTimeout(() => {
                overlay.classList.add('active');
                if (inputElem) inputElem.focus();
                if (categorySelectElem && categorySelectElem.value === NEW_CATEGORY_ID) newCategoryInputElem.focus();
            }, 10);
            overlay.addEventListener('transitionend', () => {
                if (!overlay.classList.contains('active')) document.removeEventListener('keydown', keyboardHandler);
            });
        });
    }


    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: 'Hello',
            text: USERSCRIPT_STRINGS.welcomeMessage,
            confirmButtonText: USERSCRIPT_STRINGS.welcomeGotIt
        });
    }

    function showLoadingOverlay() {
        if (!loadingOverlay) {
            loadingOverlay = document.createElement('div');
            loadingOverlay.id = 'loading-overlay';
            const spinner = document.createElement('div');
            spinner.id = 'loading-spinner';
            loadingOverlay.appendChild(spinner);
            document.body.appendChild(loadingOverlay);
        }
        setTimeout(() => loadingOverlay.classList.add('visible'), 10);
    }

    function hideLoadingOverlay() {
        if (loadingOverlay) loadingOverlay.classList.remove('visible');
    }

    function isFootball() {
        return !!document.querySelector('div#tactics_box.soccer.clearfix');
    }

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

    function insertAfterElement(newNode, referenceNode) {
        if (referenceNode && referenceNode.parentNode) {
            referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
        } else {
            console.warn("MZTM: Reference node for insertion not found or has no parent.");
        }
    }

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

    function getFormattedDate() {
        const now = new Date();
        const year = now.getFullYear();
        const month = (now.getMonth() + 1).toString().padStart(2, '0');
        const day = now.getDate().toString().padStart(2, '0');
        return `${year}-${month}-${day}`;
    }

    async function fetchTacticsFromGMStorage() {
        return GM_getValue(FORMATIONS_STORAGE_KEY, {
            tactics: []
        });
    }

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

    async function validateDuplicateTactic(id) {
        const data = await GM_getValue(FORMATIONS_STORAGE_KEY) || {
            tactics: []
        };
        return data.tactics.some(t => t.id === id);
    }

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

    async function validateDuplicateTacticWithUpdatedCoord(newId, currentTactic, data) {
        if (newId === currentTactic.id) return 'unchanged';
        else if (data.tactics.some(t => t.id === newId)) return 'duplicate';
        else return 'unique';
    }

    function loadCompleteTacticsData() {
        completeTactics = GM_getValue(COMPLETE_TACTICS_STORAGE_KEY, {});
        updateCompleteTacticsDropdown();
    }

    function saveCompleteTacticsData() {
        GM_setValue(COMPLETE_TACTICS_STORAGE_KEY, completeTactics);
    }

    async function fetchTeamIdAndUsername(forceRefresh = false) {
        const now = Date.now();
        const cachedInfo = GM_getValue(USER_INFO_CACHE_KEY);
        if (!forceRefresh && cachedInfo && cachedInfo.teamId && cachedInfo.username && (now - cachedInfo.timestamp < USER_INFO_CACHE_DURATION_MS)) {
            teamId = cachedInfo.teamId;
            username = cachedInfo.username;
            return {
                teamId,
                username
            };
        }
        try {
            const usernameElement = document.getElementById('header-username');
            if (!usernameElement) throw new Error('No username element found');
            const currentUsername = usernameElement.textContent.trim();
            const url = `/xml/manager_data.php?sport_id=1&username=${encodeURIComponent(currentUsername)}`;
            const response = await fetch(url);
            if (!response.ok) throw new Error(`HTTP error ${response.status}`);
            const xmlString = await response.text();
            const parser = new DOMParser();
            const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
            const teamElement = xmlDoc.querySelector('Team[sport="soccer"]');
            if (!teamElement) throw new Error('No soccer team data found in XML');
            const currentTeamId = teamElement.getAttribute('teamId');
            if (!currentTeamId) throw new Error('No team ID found in XML');
            teamId = currentTeamId;
            username = currentUsername;
            const newUserInfo = {
                teamId: teamId,
                username: username,
                timestamp: now
            };
            GM_setValue(USER_INFO_CACHE_KEY, newUserInfo);
            return {
                teamId,
                username
            };
        } catch (error) {
            console.error('Error fetching Team ID and Username:', error);
            showErrorMessage(USERSCRIPT_STRINGS.errorTitle, 'Could not fetch team info. Some features might be limited.');
            return {
                teamId: null,
                username: null
            };
        }
    }

    async function fetchTeamRoster(forceRefresh = false) {
        const now = Date.now();
        if (!teamId) {
            const ids = await fetchTeamIdAndUsername();
            if (!ids.teamId) {
                console.error("MZTM: Cannot fetch roster without Team ID.");
                return null;
            }
        }
        const cachedRoster = GM_getValue(ROSTER_CACHE_KEY);
        const isCacheValid = !forceRefresh && cachedRoster && cachedRoster.data && cachedRoster.teamId === teamId && (now - cachedRoster.timestamp < ROSTER_CACHE_DURATION_MS);
        if (isCacheValid) {
            return cachedRoster.data;
        }
        try {
            const url = `/xml/team_playerlist.php?sport_id=1&team_id=${teamId}`;
            const response = await fetch(url);
            if (!response.ok) throw new Error(`HTTP error ${response.status}`);
            const xmlString = await response.text();
            const parser = new DOMParser();
            const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
            const playerElements = Array.from(xmlDoc.querySelectorAll('TeamPlayers Player'));
            const roster = playerElements.map(p => p.getAttribute('id')).filter(id => id);
            if (roster.length === 0) {
                console.warn("MZTM: Fetched roster is empty for team", teamId);
            }
            rosterCache = {
                data: roster,
                timestamp: now,
                teamId: teamId
            };
            GM_setValue(ROSTER_CACHE_KEY, rosterCache);
            return roster;
        } catch (error) {
            console.error('Error fetching team roster:', error);
            showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.errorFetchingRoster);
            return null;
        }
    }

    function generateUniqueId(coordinates) {
        coordinates.sort((a, b) => {
            if (a[1] !== b[1]) return a[1] - b[1];
            else return a[0] - b[0];
        });
        const coordString = coordinates.map(coord => `${coord[0]},${coord[1]}`).join(';');
        return sha256Hash(coordString);
    }

    function handleTacticSelection(tacticName) {
        if (!tacticName) return;
        const outfieldPlayers = Array.from(document.querySelectorAll(OUTFIELD_PLAYERS_SELECTOR));
        const selectedTactic = tactics.find(td => td.name === tacticName);
        if (selectedTactic) {
            if (outfieldPlayers.length < MIN_PLAYERS_ON_PITCH - 1) {
                const hiddenTrigger = document.getElementById('hidden_trigger_button');
                if (hiddenTrigger) hiddenTrigger.click();
                setTimeout(() => rearrangePlayers(selectedTactic.coordinates), 100);
            } 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) {
            if (coordinates[i]) {
                outfieldPlayers[i].style.left = coordinates[i][0] + 'px';
                outfieldPlayers[i].style.top = coordinates[i][1] + 'px';
                removeCollision(outfieldPlayers[i]);
            }
        }
        removeTacticSlotInvalidStatus();
        updateFormationTextDisplay(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(playerElement) {
        if (playerElement.classList.contains('fieldpos-collision')) {
            playerElement.classList.remove('fieldpos-collision');
            playerElement.classList.add('fieldpos-ok');
        }
    }

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

    function updateFormationTextDisplay(formation) {
        const formationTextElement = document.querySelector(FORMATION_TEXT_SELECTOR);
        if (formationTextElement) {
            const defs = formationTextElement.querySelector('.defs'),
                  mids = formationTextElement.querySelector('.mids'),
                  atts = formationTextElement.querySelector('.atts');
            if (defs) defs.textContent = formation.defenders;
            if (mids) mids.textContent = formation.midfielders;
            if (atts) atts.textContent = formation.strikers;
        }
    }

    function getFormation(coordinates) {
        let strikers = 0,
            midfielders = 0,
            defenders = 0;
        for (const coord of coordinates) {
            const y = coord[1];
            if (y < 103) strikers++;
            else if (y <= 204) midfielders++;
            else defenders++;
        }
        return {
            strikers,
            midfielders,
            defenders
        };
    }

    function validateTacticPlayerCount(outfieldPlayers) {
        const isGoalkeeperPresent = document.querySelector(GOALKEEPER_SELECTOR);
        outfieldPlayers = outfieldPlayers.filter(p => !p.classList.contains('fieldpos-collision'));
        if (outfieldPlayers.length < MIN_PLAYERS_ON_PITCH - 1 || !isGoalkeeperPresent) {
            showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.invalidTacticError);
            return false;
        }
        return true;
    }

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

    function generateCategoryColor(name) {
        const hash = sha256Hash(name);
        const hue = parseInt(hash.substring(0, 6), 16) % 360;
        const saturation = 50 + (parseInt(hash.substring(6, 8), 16) % 30);
        const lightness = 55 + (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 && typeof storedCategories === 'object') {
            categories = storedCategories;
            if (!categories.short_passing) {
                categories.short_passing = DEFAULT_CATEGORIES.short_passing;
            }
            if (!categories.wing_play) {
                categories.wing_play = DEFAULT_CATEGORIES.wing_play;
            }
        } else {
            categories = { ...DEFAULT_CATEGORIES
                         };
            saveCategories();
        }
        if (!categories[OTHER_CATEGORY_ID]) {
            categories[OTHER_CATEGORY_ID] = {
                id: OTHER_CATEGORY_ID,
                name: 'Other',
                color: '#8395a7'
            };
        }
    }

    function loadCategoryColor(categoryId) {
        if (categories[categoryId]) return categories[categoryId].color;
        else if (categoryId === 'short_passing') return DEFAULT_CATEGORIES.short_passing.color;
        else if (categoryId === 'wing_play') return DEFAULT_CATEGORIES.wing_play.color;
        else if (categoryId === OTHER_CATEGORY_ID || !categoryId) return '#8395a7';
        else return '#8395a7';
    }

    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_CATEGORY_ID || !categoryId) return 'Other';
        else return categoryId || 'Uncategorized';
    }

    async function removeCategory(categoryId) {
        if (!categoryId || categoryId === 'all' || categoryId === OTHER_CATEGORY_ID) {
            console.error("Cannot remove category:", categoryId);
            return;
        }
        const categoryName = getCategoryName(categoryId);
        const confirmation = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle,
            text: USERSCRIPT_STRINGS.removeCategoryConfirmation.replace('{}', categoryName),
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.removeCategoryButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton,
            type: 'error'
        });
        if (!confirmation.isConfirmed) return;
        const data = await GM_getValue(FORMATIONS_STORAGE_KEY, {
            tactics: []
        });
        let updated = false;
        data.tactics = data.tactics.map(t => {
            if (t.style === categoryId) {
                t.style = OTHER_CATEGORY_ID;
                updated = true;
            }
            return t;
        });
        tactics = tactics.map(t => {
            if (t.style === categoryId) t.style = OTHER_CATEGORY_ID;
            return t;
        });
        if (updated) await GM_setValue(FORMATIONS_STORAGE_KEY, data);
        if (categoryId !== 'short_passing' && categoryId !== 'wing_play') {
            delete categories[categoryId];
            saveCategories();
        }
        if (currentFilter === categoryId) currentFilter = 'all';
        updateTacticsDropdown();
        updateFilterTabs();
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.removeCategoryAlert.replace('{}', categoryName));
    }

    async function addNewTactic() {
        const outfieldPlayers = Array.from(document.querySelectorAll(OUTFIELD_PLAYERS_SELECTOR));
        const coordinates = outfieldPlayers.map(p => [parseInt(p.style.left), parseInt(p.style.top)]);
        if (!validateTacticPlayerCount(outfieldPlayers)) return;
        const id = generateUniqueId(coordinates);
        const isDuplicate = await validateDuplicateTactic(id);
        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: (v) => {
                if (!v) return USERSCRIPT_STRINGS.noTacticNameProvidedError;
                if (v.length > MAX_TACTIC_NAME_LENGTH) return USERSCRIPT_STRINGS.tacticNameMaxLengthError;
                if (tactics.some(t => t.name === v)) return USERSCRIPT_STRINGS.alreadyExistingTacticNameError;
                return null;
            },
            showCategorySelector: true,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.addConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!result.isConfirmed || !result.value) return;
        const name = result.value;
        const categoryId = result.category.id;
        const tactic = {
            name: name,
            coordinates: coordinates,
            id: id,
            style: categoryId
        };
        await saveTacticToStorage(tactic);
        tactics.push(tactic);
        tactics.sort((a, b) => a.name.localeCompare(b.name));
        updateTacticsDropdown();
        updateFilterTabs();
        const tacticsSelector = document.getElementById('tactics_selector');
        tacticsSelector.value = tactic.name;
        handleTacticSelection(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: (v) => {
                if (!v) return USERSCRIPT_STRINGS.noTacticNameProvidedError;
                if (v.length > MAX_TACTIC_NAME_LENGTH) return USERSCRIPT_STRINGS.tacticNameMaxLengthError;
                if (tactics.some(t => t.name === v)) return USERSCRIPT_STRINGS.alreadyExistingTacticNameError;
                return null;
            },
            showCategorySelector: true,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.addConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!nameResult.isConfirmed || !nameResult.value) return;
        const name = nameResult.value;
        const categoryId = nameResult.category.id;
        try {
            const newTactic = await convertXmlToSimpleFormationJson(xml, name);
            newTactic.style = categoryId;
            const id = generateUniqueId(newTactic.coordinates);
            const isDuplicate = await validateDuplicateTactic(id);
            if (isDuplicate) {
                await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.duplicateTacticError);
                return;
            }
            newTactic.id = id;
            await saveTacticToStorage(newTactic);
            tactics.push(newTactic);
            tactics.sort((a, b) => a.name.localeCompare(b.name));
            updateTacticsDropdown();
            updateFilterTabs();
            const tacticsSelector = document.getElementById('tactics_selector');
            tacticsSelector.value = newTactic.name;
            handleTacticSelection(newTactic.name);
            await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.addAlert.replace('{}', newTactic.name));
        } catch (error) {
            console.error('XMLError:', error);
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.xmlParsingError + (error.message ? `: ${error.message}` : ''));
        }
    }

    async function deleteTactic() {
        const tacticsSelector = document.getElementById('tactics_selector');
        const selectedTactic = tactics.find(t => t.name === tacticsSelector.value);
        if (!selectedTactic) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
            return;
        }
        const confirmation = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle,
            text: USERSCRIPT_STRINGS.deleteConfirmation.replace('{}', selectedTactic.name),
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.deleteTacticConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!confirmation.isConfirmed) return;
        const deletedCategoryId = selectedTactic.style;
        const data = await GM_getValue(FORMATIONS_STORAGE_KEY) || {
            tactics: []
        };
        data.tactics = data.tactics.filter(t => t.id !== selectedTactic.id);
        await GM_setValue(FORMATIONS_STORAGE_KEY, data);
        tactics = tactics.filter(t => t.id !== selectedTactic.id);
        const categoryStillUsed = tactics.some(t => t.style === deletedCategoryId);
        if (!categoryStillUsed && deletedCategoryId && !DEFAULT_CATEGORIES[deletedCategoryId] && deletedCategoryId !== OTHER_CATEGORY_ID) {
            delete categories[deletedCategoryId];
            saveCategories();
            if (currentFilter === deletedCategoryId) currentFilter = 'all';
        }
        updateTacticsDropdown();
        updateFilterTabs();
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.deleteAlert.replace('{}', selectedTactic.name));
    }

    async function editTactic() {
        const tacticsSelector = document.getElementById('tactics_selector');
        const selectedTactic = tactics.find(t => t.name === tacticsSelector.value);
        if (!selectedTactic) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
            return;
        }
        const originalName = selectedTactic.name;
        const originalCategory = selectedTactic.style;
        const result = await showAlert({
            title: 'Edit Tactic',
            input: 'text',
            inputValue: originalName,
            placeholder: USERSCRIPT_STRINGS.tacticNamePlaceholder,
            inputValidator: (v) => {
                if (!v) return USERSCRIPT_STRINGS.noTacticNameProvidedError;
                if (v.length > MAX_TACTIC_NAME_LENGTH) return USERSCRIPT_STRINGS.tacticNameMaxLengthError;
                if (v !== originalName && tactics.some(t => t.name === v)) return USERSCRIPT_STRINGS.alreadyExistingTacticNameError;
                return null;
            },
            showCategorySelector: true,
            currentCategory: selectedTactic.style,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.updateConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!result.isConfirmed || (!result.value && !result.category)) return;
        const newName = result.value || originalName;
        const newCategory = result.category?.id || originalCategory;
        if (newName === originalName && newCategory === originalCategory) return;
        const categoryChanged = originalCategory !== newCategory;
        const data = await GM_getValue(FORMATIONS_STORAGE_KEY) || {
            tactics: []
        };
        data.tactics = data.tactics.map(t => {
            if (t.id === selectedTactic.id) {
                t.name = newName;
                t.style = newCategory;
            }
            return t;
        });
        await GM_setValue(FORMATIONS_STORAGE_KEY, data);
        tactics = tactics.map(t => {
            if (t.id === selectedTactic.id) {
                t.name = newName;
                t.style = newCategory;
            }
            return t;
        });
        if (categoryChanged) {
            const originalCategoryStillUsed = tactics.some(t => t.style === originalCategory);
            if (!originalCategoryStillUsed && originalCategory && !DEFAULT_CATEGORIES[originalCategory] && originalCategory !== OTHER_CATEGORY_ID) {
                delete categories[originalCategory];
                saveCategories();
                if (currentFilter === originalCategory) currentFilter = 'all';
            }
        }
        tactics.sort((a, b) => a.name.localeCompare(b.name));
        updateTacticsDropdown();
        updateFilterTabs();
        tacticsSelector.value = newName;
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.renameAlert.replace('{}', newName));
    }

    async function updateTactic() {
        const outfieldPlayers = Array.from(document.querySelectorAll(OUTFIELD_PLAYERS_SELECTOR));
        const tacticsSelector = document.getElementById('tactics_selector');
        const selectedTactic = tactics.find(t => t.name === tacticsSelector.value);
        if (!selectedTactic) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
            return;
        }
        if (!validateTacticPlayerCount(outfieldPlayers)) return;
        const updatedCoordinates = outfieldPlayers.map(p => [parseInt(p.style.left), parseInt(p.style.top)]);
        const newId = generateUniqueId(updatedCoordinates);
        const data = await GM_getValue(FORMATIONS_STORAGE_KEY) || {
            tactics: []
        };
        const validationOutcome = await validateDuplicateTacticWithUpdatedCoord(newId, selectedTactic, data);
        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 confirmation = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle,
            text: USERSCRIPT_STRINGS.updateConfirmation.replace('{}', selectedTactic.name),
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.updateConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!confirmation.isConfirmed) return;
        for (const tactic of data.tactics) {
            if (tactic.id === selectedTactic.id) {
                tactic.coordinates = updatedCoordinates;
                tactic.id = newId;
            }
        }
        const memoryTactic = tactics.find(t => t.id === selectedTactic.id);
        if (memoryTactic) {
            memoryTactic.coordinates = updatedCoordinates;
            memoryTactic.id = newId;
        }
        await GM_setValue(FORMATIONS_STORAGE_KEY, data);
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.updateAlert.replace('{}', selectedTactic.name));
    }

    async function clearTactics() {
        const confirmation = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle,
            text: USERSCRIPT_STRINGS.clearConfirmation,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.clearTacticsConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton,
            type: 'error'
        });
        if (!confirmation.isConfirmed) return;
        await GM_deleteValue(FORMATIONS_STORAGE_KEY);
        await GM_deleteValue(OLD_FORMATIONS_STORAGE_KEY);
        tactics = [];
        currentFilter = 'all';
        loadCategories();
        updateTacticsDropdown();
        updateFilterTabs();
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.clearAlert);
    }

    async function resetTactics() {
        const confirmation = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle,
            text: USERSCRIPT_STRINGS.resetConfirmation,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.resetTacticsConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton,
            type: 'error'
        });
        if (!confirmation.isConfirmed) return;
        await GM_deleteValue(FORMATIONS_STORAGE_KEY);
        await GM_deleteValue(OLD_FORMATIONS_STORAGE_KEY);
        await GM_deleteValue(CATEGORIES_STORAGE_KEY);
        tactics = [];
        currentFilter = 'all';
        loadCategories();
        updateTacticsDropdown();
        updateFilterTabs();
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.resetAlert);
    }

    async function importTacticsJsonData() {
        try {
            const result = await showAlert({
                title: 'Import Tactics (JSON)',
                input: 'text',
                inputValue: '',
                placeholder: 'Paste Tactics JSON here',
                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) || !importedData.tactics.every(t => t.name && t.id && Array.isArray(t.coordinates))) {
                await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.invalidImportError);
                return;
            }
            const importedTactics = importedData.tactics;
            importedTactics.forEach(t => {
                if (!t.hasOwnProperty('style')) t.style = OTHER_CATEGORY_ID;
                if (t.style && !categories[t.style] && !DEFAULT_CATEGORIES[t.style] && t.style !== OTHER_CATEGORY_ID) {
                    addCategory({
                        id: t.style,
                        name: t.style,
                        color: generateCategoryColor(t.style)
                    });
                }
            });
            let existingData = await GM_getValue(FORMATIONS_STORAGE_KEY, {
                tactics: []
            });
            let existingTactics = existingData.tactics || [];
            const mergedTactics = [...existingTactics];
            let addedCount = 0;
            for (const impTactic of importedTactics) {
                if (!existingTactics.some(t => t.id === impTactic.id)) {
                    mergedTactics.push(impTactic);
                    addedCount++;
                }
            }
            await GM_setValue(FORMATIONS_STORAGE_KEY, {
                tactics: mergedTactics
            });
            mergedTactics.sort((a, b) => a.name.localeCompare(b.name));
            tactics = mergedTactics;
            updateTacticsDropdown();
            updateFilterTabs();
            await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.importAlert + (addedCount > 0 ? ` (${addedCount} new items added)` : ''));
        } catch (error) {
            console.error('ImportError:', error);
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.invalidImportError + (error.message ? `: ${error.message}` : ''));
        }
    }

    async function exportTacticsJsonData() {
        try {
            const data = GM_getValue(FORMATIONS_STORAGE_KEY, {
                tactics: []
            });
            const jsonString = JSON.stringify(data, null, 2);
            if (navigator.clipboard?.writeText) {
                try {
                    await navigator.clipboard.writeText(jsonString);
                    await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.exportAlert);
                    return;
                } catch (clipError) {
                    console.warn('Clipboard write failed, fallback.', clipError);
                }
            }
            const textArea = document.createElement('textarea');
            textArea.value = jsonString;
            textArea.style.width = '100%';
            textArea.style.minHeight = '150px';
            textArea.style.marginTop = '10px';
            textArea.style.backgroundColor = 'rgba(0,0,0,0.2)';
            textArea.style.color = 'var(--text-color)';
            textArea.style.border = '1px solid rgba(255,255,255,0.1)';
            textArea.style.borderRadius = '4px';
            textArea.readOnly = true;
            const container = document.createElement('div');
            container.appendChild(document.createTextNode('Copy the JSON data:'));
            container.appendChild(textArea);
            await showAlert({
                title: 'Export Tactics (JSON)',
                htmlContent: container,
                confirmButtonText: 'Done'
            });
            textArea.select();
            textArea.setSelectionRange(0, 99999);
        } catch (error) {
            console.error('Export error:', error);
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, 'Failed to export tactics.');
        }
    }

    async function convertXmlToSimpleFormationJson(xmlString, tacticName) {
        const parser = new DOMParser();
        const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
        const parseErrors = xmlDoc.getElementsByTagName('parsererror');
        if (parseErrors.length > 0) throw new Error(USERSCRIPT_STRINGS.xmlValidationError);
        const positionElements = Array.from(xmlDoc.getElementsByTagName('Pos')).filter(el => el.getAttribute('pos') === 'normal');
        if (positionElements.length !== MIN_PLAYERS_ON_PITCH - 1) throw new Error(`XML must contain exactly ${MIN_PLAYERS_ON_PITCH - 1} outfield players. Found ${positionElements.length}.`);
        const coordinates = positionElements.map(el => {
            const x = parseInt(el.getAttribute('x'));
            const y = parseInt(el.getAttribute('y'));
            if (isNaN(x) || isNaN(y)) throw new Error('Invalid coordinates found in XML.');
            return [x - 7, y - 9];
        });
        return {
            name: tacticName,
            coordinates: coordinates
        };
    }

    function getAttr(element, attributeName, defaultValue = null) {
        return element ? element.getAttribute(attributeName) || defaultValue : defaultValue;
    }

    function parseCompleteTacticXml(xmlString) {
        const parser = new DOMParser();
        const xmlDoc = parser.parseFromString(xmlString, "text/xml");
        if (xmlDoc.getElementsByTagName("parsererror").length > 0) throw new Error("XML parsing error");
        const soccerTactics = xmlDoc.querySelector("SoccerTactics");
        if (!soccerTactics) throw new Error("Missing <SoccerTactics>");
        const teamElement = soccerTactics.querySelector("Team");
        const posElements = Array.from(soccerTactics.querySelectorAll("Pos"));
        const subElements = Array.from(soccerTactics.querySelectorAll("Sub"));
        const ruleElements = Array.from(soccerTactics.querySelectorAll("TacticRule"));
        const data = {
            initialCoords: [],
            alt1Coords: [],
            alt2Coords: [],
            teamSettings: {},
            substitutes: [],
            tacticRules: [],
            originalPlayerIDs: new Set()
        };
        data.teamSettings = {
            passingStyle: getAttr(teamElement, 'tactics', 'shortpass'),
            mentality: getAttr(teamElement, 'playstyle', 'normal'),
            aggression: getAttr(teamElement, 'aggression', 'normal'),
            captainPID: getAttr(teamElement, 'captain', '0')
        };
        if (data.teamSettings.captainPID !== '0') data.originalPlayerIDs.add(data.teamSettings.captainPID);
        posElements.forEach(el => {
            const pid = getAttr(el, 'pid');
            const posType = getAttr(el, 'pos');
            if (!pid) return;
            data.originalPlayerIDs.add(pid);
            if (posType === 'normal' || posType === 'goalie') {
                const x = parseInt(getAttr(el, 'x', 0));
                const y = parseInt(getAttr(el, 'y', 0));
                const x1 = parseInt(getAttr(el, 'x1', x));
                const y1 = parseInt(getAttr(el, 'y1', y));
                const x2 = parseInt(getAttr(el, 'x2', x1));
                const y2 = parseInt(getAttr(el, 'y2', y1));
                data.initialCoords.push({
                    pid: pid,
                    pos: posType,
                    x: x,
                    y: y
                });
                data.alt1Coords.push({
                    pid: pid,
                    pos: posType,
                    x: x1,
                    y: y1
                });
                data.alt2Coords.push({
                    pid: pid,
                    pos: posType,
                    x: x2,
                    y: y2
                });
            }
        });
        subElements.forEach(el => {
            const pid = getAttr(el, 'pid');
            const posType = getAttr(el, 'pos');
            const x = parseInt(getAttr(el, 'x', 0));
            const y = parseInt(getAttr(el, 'y', 0));
            if (pid) {
                data.originalPlayerIDs.add(pid);
                data.substitutes.push({
                    pid: pid,
                    pos: posType,
                    x: x,
                    y: y
                });
            }
        });
        ruleElements.forEach(el => {
            const rule = {};
            for (const attr of el.attributes) {
                rule[attr.name] = attr.value;
                if (attr.name === 'out_player' && attr.value !== 'no_change' && attr.value !== '0') data.originalPlayerIDs.add(attr.value);
                if (attr.name === 'in_player_id' && attr.value !== 'NULL' && attr.value !== '0') data.originalPlayerIDs.add(attr.value);
            }
            data.tacticRules.push(rule);
        });
        data.originalPlayerIDs = Array.from(data.originalPlayerIDs);
        return data;
    }

    function generateCompleteTacticXml(tacticData, playerMapping) {
        let xml = `<?xml version="1.0" ?>\n<SoccerTactics>\n`;
        const mappedCaptain = playerMapping[tacticData.teamSettings.captainPID] || '0';
        xml += `\t<Team tactics="${tacticData.teamSettings.passingStyle || 'shortpass'}" playstyle="${tacticData.teamSettings.mentality || 'normal'}" aggression="${tacticData.teamSettings.aggression || 'normal'}" captain="${mappedCaptain}" />\n`;
        const playerCoords = {};
        tacticData.initialCoords.forEach(p => {
            if (!playerCoords[p.pid]) {
                playerCoords[p.pid] = {
                    pos: p.pos
                };
                playerCoords[p.pid].initial = {
                    x: p.x,
                    y: p.y
                };
            }
        });
        tacticData.alt1Coords.forEach(p => {
            if (!playerCoords[p.pid]) return;
            playerCoords[p.pid].alt1 = {
                x: p.x,
                y: p.y
            };
        });
        tacticData.alt2Coords.forEach(p => {
            if (!playerCoords[p.pid]) return;
            playerCoords[p.pid].alt2 = {
                x: p.x,
                y: p.y
            };
        });
        for (const originalPid in playerCoords) {
            const mappedPid = playerMapping[originalPid];
            if (!mappedPid) continue;
            const playerData = playerCoords[originalPid];
            const initial = playerData.initial || {
                x: 0,
                y: 0
            };
            const alt1 = playerData.alt1 || initial;
            const alt2 = playerData.alt2 || alt1;
            xml += `\t<Pos pos="${playerData.pos}" pid="${mappedPid}" x="${initial.x}" y="${initial.y}" x1="${alt1.x}" y1="${alt1.y}" x2="${alt2.x}" y2="${alt2.y}" />\n`;
        }
        tacticData.substitutes.forEach(s => {
            const mappedPid = playerMapping[s.pid];
            if (mappedPid) xml += `\t<Sub pos="${s.pos}" pid="${mappedPid}" x="${s.x}" y="${s.y}" />\n`;
        });
        tacticData.tacticRules.forEach(rule => {
            const mappedOutPlayer = (rule.out_player && rule.out_player !== 'no_change') ? (playerMapping[rule.out_player] || 'no_change') : 'no_change';
            const mappedInPlayer = (rule.in_player_id && rule.in_player_id !== 'NULL') ? (playerMapping[rule.in_player_id] || 'NULL') : 'NULL';
            let includeRule = true;
            if (rule.out_player && rule.out_player !== 'no_change' && mappedOutPlayer === 'no_change') includeRule = false;
            if (rule.in_player_id && rule.in_player_id !== 'NULL' && mappedInPlayer === 'NULL') includeRule = false;
            if (includeRule) {
                xml += '\t<TacticRule';
                for (const attr in rule) {
                    let value = rule[attr];
                    if (attr === 'out_player') value = mappedOutPlayer;
                    if (attr === 'in_player_id') value = mappedInPlayer;
                    xml += ` ${attr}="${value}"`;
                }
                xml += ' />\n';
            }
        });
        xml += '</SoccerTactics>';
        return xml;
    }

    async function saveCompleteTactic() {
        const exportButton = document.getElementById('export_button');
        const importExportWindow = document.getElementById('importExportTacticsWindow');
        const playerInfoWindow = document.getElementById('playerInfoWindow');
        const importExportData = document.getElementById('importExportData');
        if (!exportButton || !importExportWindow || !playerInfoWindow || !importExportData) return showErrorMessage(USERSCRIPT_STRINGS.errorTitle, 'Could not find required MZ UI elements for export.');
        const windowHidden = importExportWindow.style.display === 'none';
        if (windowHidden) {
            const toggleButton = document.getElementById('import_export_button');
            if (toggleButton) toggleButton.click();
            else return showErrorMessage(USERSCRIPT_STRINGS.errorTitle, 'Could not find button to toggle XML view.');
        }
        importExportData.value = '';
        exportButton.click();
        await new Promise(r => setTimeout(r, 200));
        const xmlString = importExportData.value;
        if (!xmlString) {
            if (windowHidden) document.getElementById('close_button')?.click();
            return showErrorMessage(USERSCRIPT_STRINGS.errorTitle, 'Export did not produce XML.');
        }
        let savedData;
        try {
            savedData = parseCompleteTacticXml(xmlString);
        } catch (error) {
            console.error("XML Parse Error:", error);
            if (windowHidden) document.getElementById('close_button')?.click();
            return showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.errorXmlExportParse);
        }
        const result = await showAlert({
            title: USERSCRIPT_STRINGS.completeTacticNamePrompt,
            input: 'text',
            inputValue: '',
            placeholder: USERSCRIPT_STRINGS.completeTacticNamePlaceholder,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.addConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton,
            inputValidator: (v) => {
                if (!v) return USERSCRIPT_STRINGS.noTacticNameProvidedError;
                if (v.length > MAX_TACTIC_NAME_LENGTH) return USERSCRIPT_STRINGS.tacticNameMaxLengthError;
                return null;
            },
        });
        if (!result.isConfirmed || !result.value) {
            if (windowHidden) document.getElementById('close_button')?.click();
            return;
        }
        const baseName = result.value;
        const fullName = `${baseName} (${getFormattedDate()})`;
        completeTactics[fullName] = savedData;
        saveCompleteTacticsData();
        updateCompleteTacticsDropdown();
        if (windowHidden) document.getElementById('close_button')?.click();
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.completeTacticSaveSuccess.replace('{}', fullName));
    }

    async function loadCompleteTactic() {
        const selector = document.getElementById('complete_tactics_selector');
        const selectedName = selector.value;
        if (!selectedName || !completeTactics[selectedName]) return showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
        showLoadingOverlay();
        const originalAlert = window.alert;
        try {
            const dataToLoad = completeTactics[selectedName];
            const currentRoster = await fetchTeamRoster();
            if (!currentRoster) throw new Error(USERSCRIPT_STRINGS.errorFetchingRoster);
            const rosterSet = new Set(currentRoster);
            const originalPids = dataToLoad.originalPlayerIDs || [];
            const mapping = {};
            const missingPids = [];
            const mappedPids = new Set();
            originalPids.forEach(pid => {
                if (rosterSet.has(pid)) {
                    mapping[pid] = pid;
                    mappedPids.add(pid);
                } else {
                    missingPids.push(pid);
                }
            });
            const availablePids = currentRoster.filter(pid => !mappedPids.has(pid));
            let replacementsFound = 0;
            missingPids.forEach(missingPid => {
                if (availablePids.length > 0) {
                    const randomIndex = Math.floor(Math.random() * availablePids.length);
                    const replacementPid = availablePids.splice(randomIndex, 1)[0];
                    mapping[missingPid] = replacementPid;
                    replacementsFound++;
                } else {
                    mapping[missingPid] = null;
                }
            });
            const assignedPids = new Set();
            dataToLoad.initialCoords.forEach(p => {
                if (mapping[p.pid]) assignedPids.add(mapping[p.pid]);
            });
            dataToLoad.substitutes.forEach(s => {
                if (mapping[s.pid]) assignedPids.add(mapping[s.pid]);
            });
            if (assignedPids.size < MIN_PLAYERS_ON_PITCH) throw new Error(USERSCRIPT_STRINGS.errorInsufficientPlayers);
            let xmlString;
            try {
                xmlString = generateCompleteTacticXml(dataToLoad, mapping);
            } catch (error) {
                console.error("XML Gen Error:", error);
                throw new Error(USERSCRIPT_STRINGS.errorXmlGenerate);
            }
            let alertContent = null;
            window.alert = (msg) => {
                console.warn("Native alert captured:", msg);
                alertContent = msg;
            };
            const importButton = document.getElementById('import_button');
            const importExportWindow = document.getElementById('importExportTacticsWindow');
            const importExportData = document.getElementById('importExportData');
            if (!importButton || !importExportWindow || !importExportData) throw new Error('Could not find required MZ UI elements for import.');
            const windowHidden = importExportWindow.style.display === 'none';
            if (windowHidden) {
                document.getElementById('import_export_button')?.click();
                await new Promise(r => setTimeout(r, 50));
            }
            importExportData.value = xmlString;
            importButton.click();
            await new Promise(r => setTimeout(r, 300));
            window.alert = originalAlert;
            if (alertContent) throw new Error(USERSCRIPT_STRINGS.invalidXmlForImport + (alertContent.length < 100 ? ` MZ Message: ${alertContent}` : ''));
            let updatedStoredTactic = JSON.parse(JSON.stringify(dataToLoad));
            updatedStoredTactic.originalPlayerIDs = originalPids;
            updatedStoredTactic.mappedPlayerIDs = mapping;
            updatedStoredTactic.teamSettings.captainPID = mapping[dataToLoad.teamSettings.captainPID] || '0';
            updatedStoredTactic.substitutes = dataToLoad.substitutes.map(s => ({ ...s,
                                                                                pid: mapping[s.pid]
                                                                               })).filter(s => s.pid);
            updatedStoredTactic.tacticRules = dataToLoad.tacticRules.map(r => {
                const newRule = { ...r
                                };
                if (newRule.out_player && newRule.out_player !== 'no_change') newRule.out_player = mapping[r.out_player] || 'no_change';
                if (newRule.in_player_id && newRule.in_player_id !== 'NULL') newRule.in_player_id = mapping[r.in_player_id] || 'NULL';
                return newRule;
            }).filter(r => {
                const originalRule = dataToLoad.tacticRules.find(or => or.name === r.name);
                const outPlayerInvalid = originalRule?.out_player !== 'no_change' && r.out_player === 'no_change';
                const inPlayerInvalid = originalRule?.in_player_id !== 'NULL' && r.in_player_id === 'NULL';
                return !(outPlayerInvalid || inPlayerInvalid);
            });
            completeTactics[selectedName] = updatedStoredTactic;
            saveCompleteTacticsData();
            const observer = new MutationObserver((mutationsList, obs) => {
                for (const mutation of mutationsList) {
                    if (mutation.type === 'childList') {
                        const errorBox = document.getElementById('lightbox_tactics_rule_error');
                        if (errorBox && errorBox.style.display !== 'none') {
                            const okButton = errorBox.querySelector('#powerbox_confirm_ok_button');
                            if (okButton) {
                                okButton.click();
                                obs.disconnect();
                                break;
                            }
                        }
                    }
                }
            });
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
            setTimeout(() => observer.disconnect(), 3000);
            if (replacementsFound > 0) {
                showAlert({
                    title: 'Warning',
                    text: USERSCRIPT_STRINGS.warningPlayersSubstituted,
                    type: 'info'
                });
            }
            else showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.completeTacticLoadSuccess.replace('{}', selectedName));
        } catch (error) {
            console.error("Load Complete Tactic Error:", error);
            showErrorMessage(USERSCRIPT_STRINGS.errorTitle, error.message || 'Unknown error during load.');
            if (window.alert !== originalAlert) window.alert = originalAlert;
        } finally {
            hideLoadingOverlay();
        }
    }

    async function deleteCompleteTactic() {
        const selector = document.getElementById('complete_tactics_selector');
        const selectedName = selector.value;
        if (!selectedName || !completeTactics[selectedName]) return showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
        const confirmation = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle,
            text: USERSCRIPT_STRINGS.deleteConfirmation.replace('{}', selectedName),
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.deleteTacticConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton,
            type: 'error'
        });
        if (!confirmation.isConfirmed) return;
        delete completeTactics[selectedName];
        saveCompleteTacticsData();
        updateCompleteTacticsDropdown();
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.completeTacticDeleteSuccess.replace('{}', selectedName));
    }

    function createTacticsSelector() {
        const container = document.createElement('div');
        container.className = 'tactics-selector-section';
        const dropdownContainer = document.createElement('div');
        dropdownContainer.className = 'tactics-dropdown-container';
        const dropdownWrapper = document.createElement('div');
        dropdownWrapper.className = 'tactics-dropdown-wrapper';
        const selectElement = document.createElement('select');
        selectElement.id = 'tactics_selector';
        selectElement.addEventListener('change', function() {
            handleTacticSelection(this.value);
        });
        dropdownWrapper.appendChild(selectElement);
        dropdownContainer.appendChild(dropdownWrapper);
        const searchBox = document.createElement('input');
        searchBox.type = 'text';
        searchBox.className = 'tactics-search-box';
        searchBox.placeholder = USERSCRIPT_STRINGS.searchPlaceholder;
        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';
        dropdownContainer.appendChild(filterTabs);
        container.appendChild(dropdownContainer);
        return container;
    }

    function createFilterTab(categoryId, label, isActive = false, isRemovable = false) {
        const tab = document.createElement('button');
        tab.className = 'tactics-filter-tab';
        if (isActive) tab.classList.add('active');
        const labelSpan = document.createElement('span');
        labelSpan.textContent = label;
        tab.appendChild(labelSpan);
        tab.dataset.filter = categoryId;
        const categoryColor = loadCategoryColor(categoryId);
        tab.style.setProperty('--category-color', categoryColor);
        tab.addEventListener('click', (e) => {
            if (e.target.classList.contains('remove-category-btn')) return;
            document.querySelectorAll('.tactics-filter-tab').forEach(tb => tb.classList.remove('active'));
            tab.classList.add('active');
            currentFilter = categoryId;
            updateTacticsDropdown();
        });
        if (isRemovable) {
            const removeButton = document.createElement('span');
            removeButton.className = 'remove-category-btn';
            removeButton.textContent = 'x';
            removeButton.title = `Remove category "${label}"`;
            removeButton.addEventListener('click', (e) => {
                e.stopPropagation();
                removeCategory(categoryId).catch(console.error);
            });
            tab.appendChild(removeButton);
            tab.style.paddingRight = '5px';
        }
        return tab;
    }

    function updateFilterTabs() {
        const filterTabsContainer = document.getElementById('tactics-filter-tabs');
        if (!filterTabsContainer) return;
        filterTabsContainer.innerHTML = '';
        const usedCategoryIds = new Set(tactics.map(t => t.style || OTHER_CATEGORY_ID));
        let categoriesToShow = [{
            id: 'all',
            name: USERSCRIPT_STRINGS.allTacticsFilter,
            removable: false
        }];
        if (usedCategoryIds.has(OTHER_CATEGORY_ID) || currentFilter === OTHER_CATEGORY_ID) {
            categoriesToShow.push({
                id: OTHER_CATEGORY_ID,
                name: getCategoryName(OTHER_CATEGORY_ID),
                removable: false
            });
        }
        const otherCategoryInfo = Object.keys(categories).filter(id => id !== 'all' && id !== OTHER_CATEGORY_ID && usedCategoryIds.has(id)).map(id => ({
            id: id,
            name: getCategoryName(id),
            removable: (!DEFAULT_CATEGORIES[id])
        })).sort((a, b) => a.name.localeCompare(b.name));
        categoriesToShow = categoriesToShow.concat(otherCategoryInfo);
        categoriesToShow.forEach(categoryInfo => {
            const isActive = currentFilter === categoryInfo.id;
            const tab = createFilterTab(categoryInfo.id, categoryInfo.name, isActive, categoryInfo.removable);
            if (categoryInfo.id === 'all' && isActive) tab.style.setProperty('--category-color', 'rgba(255,255,255,0.15)');
            filterTabsContainer.appendChild(tab);
        });
        if (!categoriesToShow.some(cat => cat.id === currentFilter)) {
            currentFilter = 'all';
            const allTab = filterTabsContainer.querySelector('.tactics-filter-tab[data-filter="all"]');
            if (allTab) allTab.classList.add('active');
        }
    }

    function updateTacticsDropdown() {
        const selectElement = document.getElementById('tactics_selector');
        const dropdownWrapper = selectElement?.closest('.tactics-dropdown-wrapper');
        const searchBox = document.querySelector('.tactics-search-box');
        if (!selectElement) return;
        const previousSelectedValue = selectElement.value;
        selectElement.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 = USERSCRIPT_STRINGS.tacticsDropdownMenuLabel;
        placeholderOption.disabled = true;
        placeholderOption.selected = true;
        selectElement.appendChild(placeholderOption);
        const filteredTactics = tactics.filter(t => {
            const nameMatch = searchTerm === '' || t.name.toLowerCase().includes(searchTerm);
            const categoryMatch = currentFilter === 'all' || (currentFilter === OTHER_CATEGORY_ID && (!t.style || t.style === OTHER_CATEGORY_ID)) || t.style === currentFilter;
            return nameMatch && categoryMatch;
        });
        const groupedTactics = {};
        Object.keys(categories).forEach(id => {
            groupedTactics[id] = [];
        });
        if (!groupedTactics[OTHER_CATEGORY_ID]) groupedTactics[OTHER_CATEGORY_ID] = [];
        filteredTactics.forEach(t => {
            const categoryId = t.style || OTHER_CATEGORY_ID;
            if (!groupedTactics[categoryId]) groupedTactics[OTHER_CATEGORY_ID].push(t);
            else groupedTactics[categoryId].push(t);
        });
        const categoryOrder = Object.keys(groupedTactics).filter(id => groupedTactics[id].length > 0).sort((a, b) => {
            if (a === currentFilter) return -1;
            if (b === currentFilter) return 1;
            if (a === OTHER_CATEGORY_ID) return 1;
            if (b === OTHER_CATEGORY_ID) return -1;
            return (getCategoryName(a) || '').localeCompare(getCategoryName(b) || '');
        });
        categoryOrder.forEach(categoryId => {
            if (groupedTactics[categoryId].length > 0) addTacticOptionsGroup(selectElement, groupedTactics[categoryId], getCategoryName(categoryId));
        });
        if (filteredTactics.length === 0 && tactics.length > 0) {
            const noTacticsOption = document.createElement('option');
            noTacticsOption.disabled = true;
            noTacticsOption.textContent = USERSCRIPT_STRINGS.noTacticsFound;
            selectElement.appendChild(noTacticsOption);
            placeholderOption.selected = false;
        } else if (filteredTactics.length === 0 && tactics.length === 0) {
            placeholderOption.textContent = 'No tactics saved';
        }
        let foundPrevious = false;
        for (let i = 0; i < selectElement.options.length; i++) {
            if (selectElement.options[i].value === previousSelectedValue) {
                selectElement.selectedIndex = i;
                foundPrevious = true;
                break;
            }
        }
        if (!foundPrevious) selectElement.selectedIndex = 0;
        selectElement.disabled = tactics.length === 0;
    }

    function addTacticOptionsGroup(selectElement, tacticsList, groupLabel) {
        if (tacticsList.length === 0) return;
        const optgroup = document.createElement('optgroup');
        optgroup.label = groupLabel;
        selectElement.appendChild(optgroup);
        tacticsList.sort((a, b) => a.name.localeCompare(b.name));
        tacticsList.forEach(tactic => {
            const option = document.createElement('option');
            option.value = tactic.name;
            option.dataset.style = tactic.style || OTHER_CATEGORY_ID;
            option.textContent = tactic.name;
            optgroup.appendChild(option);
        });
    }

    function createCompleteTacticsSelector() {
        const container = document.createElement('div');
        container.className = 'tactics-selector-section';
        const label = document.createElement('label');
        label.textContent = '';
        label.className = 'tactics-selector-label';
        const selectElement = document.createElement('select');
        selectElement.id = 'complete_tactics_selector';
        const placeholderOption = document.createElement('option');
        placeholderOption.value = '';
        placeholderOption.textContent = USERSCRIPT_STRINGS.tacticsDropdownMenuLabel;
        placeholderOption.disabled = true;
        placeholderOption.selected = true;
        selectElement.appendChild(placeholderOption);
        container.appendChild(label);
        container.appendChild(selectElement);
        return container;
    }

    function updateCompleteTacticsDropdown() {
        const selectElement = document.getElementById('complete_tactics_selector');
        if (!selectElement) return;
        const previousSelectedValue = selectElement.value;
        while (selectElement.options.length > 1) selectElement.remove(1);
        const names = Object.keys(completeTactics).sort((a, b) => a.localeCompare(b));
        if (names.length === 0) {
            selectElement.options[0].textContent = 'No tactics saved';
            selectElement.disabled = true;
        } else {
            selectElement.options[0].textContent = USERSCRIPT_STRINGS.tacticsDropdownMenuLabel;
            selectElement.disabled = false;
            names.forEach(name => {
                const option = document.createElement('option');
                option.value = name;
                option.textContent = name;
                selectElement.appendChild(option);
            });
        }
        let foundPrevious = false;
        for (let i = 0; i < selectElement.options.length; i++) {
            if (selectElement.options[i].value === previousSelectedValue) {
                selectElement.selectedIndex = i;
                foundPrevious = true;
                break;
            }
        }
        if (!foundPrevious) selectElement.selectedIndex = 0;
    }

    function createButton(id, text, clickHandler) {
        const button = document.createElement('button');
        setUpButton(button, id, text);
        if (clickHandler) {
            button.addEventListener('click', async (e) => {
                e.stopPropagation();
                try {
                    await clickHandler();
                } catch (err) {
                    console.error('Button click failed:', err);
                    showErrorMessage('Action Failed', `${err.message || err}`);
                }
            });
        }
        return button;
    }

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

    function createModeToggleSwitch() {
        const label = document.createElement('label');
        label.className = 'mode-toggle-switch';
        const input = document.createElement('input');
        input.type = 'checkbox';
        input.id = 'view-mode-toggle';
        input.addEventListener('change', (e) => setViewMode(e.target.checked ? 'complete' : 'normal'));
        const slider = document.createElement('span');
        slider.className = 'mode-toggle-slider';
        label.appendChild(input);
        label.appendChild(slider);
        return label;
    }

    function createModeLabel(mode, isPrefix = false) {
        const span = document.createElement('span');
        span.className = 'mode-toggle-label';
        span.textContent = isPrefix ? USERSCRIPT_STRINGS.modeLabel : (mode === 'normal' ? USERSCRIPT_STRINGS.normalModeLabel : USERSCRIPT_STRINGS.completeModeLabel);
        span.id = `mode-label-${mode}`;
        return span;
    }

    function setViewMode(mode) {
        currentViewMode = mode;
        GM_setValue(VIEW_MODE_KEY, mode);
        const normalContent = document.getElementById('normal-tactics-content');
        const completeContent = document.getElementById('complete-tactics-content');
        const toggleInput = document.getElementById('view-mode-toggle');
        const normalLabel = document.getElementById('mode-label-normal');
        const completeLabel = document.getElementById('mode-label-complete');
        const isNormal = mode === 'normal';
        if (normalContent) normalContent.style.display = isNormal ? 'block' : 'none';
        if (completeContent) completeContent.style.display = isNormal ? 'none' : 'block';
        if (toggleInput) toggleInput.checked = !isNormal;
        if (normalLabel) normalLabel.classList.toggle('active', isNormal);
        if (completeLabel) completeLabel.classList.toggle('active', !isNormal);
    }

    function createMainContainer() {
        const container = document.createElement('div');
        container.id = 'mz_tactics_panel';
        container.classList.add('mz-panel');
        const header = document.createElement('div');
        header.classList.add('mz-group-main-title');
        const titleContainer = document.createElement('div');
        titleContainer.className = 'mz-title-container';
        const titleText = document.createElement('span');
        titleText.textContent = USERSCRIPT_STRINGS.managerTitle;
        titleText.classList.add('mz-main-title');
        const versionText = document.createElement('span');
        versionText.textContent = 'v' + DISPLAY_VERSION;
        versionText.classList.add('mz-version-text');
        const modeToggleContainer = document.createElement('div');
        modeToggleContainer.className = 'mode-toggle-container';
        const prefixLabel = createModeLabel('', true);
        const modeLabelNormal = createModeLabel('normal');
        const toggleSwitch = createModeToggleSwitch();
        const modeLabelComplete = createModeLabel('complete');
        appendChildren(modeToggleContainer, [prefixLabel, modeLabelNormal, toggleSwitch, modeLabelComplete]);
        appendChildren(titleContainer, [titleText, versionText, modeToggleContainer]);
        header.appendChild(titleContainer);
        const toggleButton = createToggleButton();
        header.appendChild(toggleButton);
        container.appendChild(header);
        const group = document.createElement('div');
        group.classList.add('mz-group');
        container.appendChild(group);
        const normalContent = document.createElement('div');
        normalContent.id = 'normal-tactics-content';
        normalContent.className = 'section-content';
        const tacticsSelectorSection = createTacticsSelector();
        const normalButtonsSection = document.createElement('div');
        normalButtonsSection.className = 'action-buttons-section';
        const addCurrentBtn = createButton('add_current_tactic_btn', USERSCRIPT_STRINGS.addCurrentTactic, addNewTactic);
        const addXmlBtn = createButton('add_xml_tactic_btn', USERSCRIPT_STRINGS.addWithXmlButton, addNewTacticWithXml);
        const editBtn = createButton('edit_tactic_button', USERSCRIPT_STRINGS.renameButton, editTactic);
        const updateBtn = createButton('update_tactic_button', USERSCRIPT_STRINGS.updateButton, updateTactic);
        const deleteBtn = createButton('delete_tactic_button', USERSCRIPT_STRINGS.deleteButton, deleteTactic);
        const importBtn = createButton('import_tactics_btn', USERSCRIPT_STRINGS.importButton, importTacticsJsonData);
        const exportBtn = createButton('export_tactics_btn', USERSCRIPT_STRINGS.exportButton, exportTacticsJsonData);
        const resetBtn = createButton('reset_tactics_btn', USERSCRIPT_STRINGS.resetButton, resetTactics);
        const clearBtn = createButton('clear_tactics_btn', USERSCRIPT_STRINGS.clearButton, clearTactics);
        const normalButtonsRow1 = document.createElement('div');
        normalButtonsRow1.className = 'action-buttons-row';
        appendChildren(normalButtonsRow1, [addCurrentBtn, addXmlBtn, editBtn, updateBtn, deleteBtn]);
        const normalButtonsRow2 = document.createElement('div');
        normalButtonsRow2.className = 'action-buttons-row';
        appendChildren(normalButtonsRow2, [importBtn, exportBtn, resetBtn, clearBtn]);
        appendChildren(normalButtonsSection, [normalButtonsRow1, normalButtonsRow2]);
        appendChildren(normalContent, [tacticsSelectorSection, normalButtonsSection, createHiddenTriggerButton(), createCombinedInfoButton()]);
        group.appendChild(normalContent);
        const completeContent = document.createElement('div');
        completeContent.id = 'complete-tactics-content';
        completeContent.className = 'section-content';
        completeContent.style.display = 'none';
        const completeTacticsSelectorSection = createCompleteTacticsSelector();
        const completeButtonsSection = document.createElement('div');
        completeButtonsSection.className = 'action-buttons-section';
        const completeButtonsRow = document.createElement('div');
        completeButtonsRow.className = 'action-buttons-row';
        const saveCompleteBtn = createButton('save_complete_tactic_button', USERSCRIPT_STRINGS.saveCompleteTacticButton, saveCompleteTactic);
        const loadCompleteBtn = createButton('load_complete_tactic_button', USERSCRIPT_STRINGS.loadCompleteTacticButton, loadCompleteTactic);
        const deleteCompleteBtn = createButton('delete_complete_tactic_button', USERSCRIPT_STRINGS.deleteCompleteTacticButton, deleteCompleteTactic);
        appendChildren(completeButtonsRow, [saveCompleteBtn, loadCompleteBtn, deleteCompleteBtn]);
        completeButtonsSection.appendChild(completeButtonsRow);
        appendChildren(completeContent, [completeTacticsSelectorSection, completeButtonsSection, createCombinedInfoButton()]);
        group.appendChild(completeContent);
        return container;
    }

    function createHiddenTriggerButton() {
        const button = document.createElement('button');
        button.id = 'hidden_trigger_button';
        button.textContent = '';
        button.style.cssText = 'position:absolute; opacity:0; pointer-events:none; width:0; height:0; padding:0; margin:0; border:0;';
        button.addEventListener('click', function() {
            const presetSelect = document.getElementById('tactics_preset');
            if (presetSelect) {
                presetSelect.value = '5-3-2';
                presetSelect.dispatchEvent(new Event('change'));
            }
        });
        return button;
    }

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

    function createModalTabs(tabsConfig, modalBody) {
        const tabsContainer = document.createElement('div');
        tabsContainer.className = 'modal-tabs';
        tabsConfig.forEach((tab, index) => {
            const tabButton = document.createElement('button');
            tabButton.className = 'modal-tab';
            tabButton.textContent = tab.title;
            tabButton.dataset.tabId = tab.id;
            if (index === 0) tabButton.classList.add('active');
            tabButton.addEventListener('click', () => {
                modalBody.querySelectorAll('.modal-tab').forEach(t => t.classList.remove('active'));
                modalBody.querySelectorAll('.modal-tab-content').forEach(c => c.classList.remove('active'));
                tabButton.classList.add('active');
                const content = modalBody.querySelector(`.modal-tab-content[data-tab-id="${tab.id}"]`);
                if (content) content.classList.add('active');
            });
            tabsContainer.appendChild(tabButton);
        });
        return tabsContainer;
    }

    function createTabbedModalContent(tabsConfig) {
        const wrapper = document.createElement('div');
        wrapper.className = 'modal-info-wrapper';
        const tabs = createModalTabs(tabsConfig, wrapper);
        wrapper.appendChild(tabs);
        tabsConfig.forEach((tab, index) => {
            const contentDiv = document.createElement('div');
            contentDiv.className = 'modal-tab-content';
            contentDiv.dataset.tabId = tab.id;
            if (index === 0) contentDiv.classList.add('active');
            const content = tab.contentGenerator();
            contentDiv.appendChild(content);
            wrapper.appendChild(contentDiv);
        });
        return wrapper;
    }

    function createAboutTabContent() {
        const content = document.createElement('div');
        const aboutSection = document.createElement('div');
        const aboutTitle = document.createElement('h3');
        aboutTitle.textContent = 'About';
        const infoText = document.createElement('p');
        infoText.id = 'info_modal_info_text';
        infoText.innerHTML = USERSCRIPT_STRINGS.modalContentInfoText;
        const feedbackText = document.createElement('p');
        feedbackText.id = 'info_modal_feedback_text';
        feedbackText.innerHTML = USERSCRIPT_STRINGS.modalContentFeedbackText;
        appendChildren(aboutSection, [aboutTitle, infoText, feedbackText]);
        content.appendChild(aboutSection);
        const faqSection = document.createElement('div');
        faqSection.className = 'faq-section';
        const faqTitle = document.createElement('h3');
        faqTitle.textContent = 'FAQ/Function Explanations';
        faqSection.appendChild(faqTitle);
        const faqItems = [{
            q: "<code>Normal</code>/<code>Complete</code> Toggle",
            a: "Switches between managing basic player positions and managing the complete tactic including alternatives, rules, and team settings like passing style/mentality (Complete)."
        }, {
            q: "<code>Add Current</code> Button",
            a: "Saves the player positions currently visible on the pitch as a new tactic. You'll be prompted for a name and category."
        }, {
            q: "<code>Add via XML</code> Button",
            a: "Allows pasting XML to add a new tactic. Only player positions are saved from the XML. Prompted for name/category."
        }, {
            q: "<code>Edit</code> Button",
            a: "Allows renaming the selected tactic and/or changing its assigned category."
        }, {
            q: "<code>Save Positions</code> Button",
            a: "Updates the coordinates of the selected tactic to match the current player positions on the pitch."
        }, {
            q: "<code>Delete</code> Button",
            a: "Permanently removes the selected tactic from the storage."
        }, {
            q: "<code>Import</code> Button",
            a: "Imports multiple tactics from a JSON text format (usually obtained via the Export button). Merges with existing tactics."
        }, {
            q: "<code>Export</code> Button",
            a: "Exports all saved tactics into a JSON text format, which can be backed up or shared (data is copied to the clipboard)."
        }, {
            q: "<code>Reset</code> Button",
            a: "Resets to the script default tactics."
        }, {
            q: "<code>Clear</code> Button",
            a: "Deletes all saved tactics."
        }, {
            q: "<code>Save Current</code> Button (Complete Mode)",
            a: "Exports the entire current tactic setup (positions, alts, tactic rules, settings) using MZ's native export, parses it and saves it as a complete tactic."
        }, {
            q: "<code>Load</code> Button (Complete Mode)",
            a: "Loads a saved complete tactic using MZ's native import. Tries to match players based on stored IDs; substitutes available roster players if originals are missing. Updates positions, alts, tactic rules and settings."
        }];
        faqItems.forEach(item => {
            const faqItemDiv = document.createElement('div');
            faqItemDiv.className = 'faq-item';
            const question = document.createElement('h4');
            question.innerHTML = item.q;
            const answer = document.createElement('p');
            answer.textContent = item.a;
            appendChildren(faqItemDiv, [question, answer]);
            faqSection.appendChild(faqItemDiv);
        });
        content.appendChild(faqSection);
        return content;
    }

    function createLinksTabContent() {
        const content = document.createElement('div');
        const linksSection = document.createElement('div');
        const linksTitle = document.createElement('h3');
        linksTitle.textContent = 'Useful Links';
        const resourcesText = createUsefulContent();
        const linksMap = 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/en/scripts/373382-van-mz-playeradvanced'],
            ['Mazyar Userscript', 'https://greasyfork.org/en/scripts/476290-mazyar'],
            ['Stats Xente Userscript', 'https://greasyfork.org/en/scripts/491442-stats-xente-script'],
            ['More userscripts', 'https://greasyfork.org/en/users/1088808-douglasdotv']
        ]);
        const linksList = createLinksList(linksMap);
        appendChildren(linksSection, [linksTitle, resourcesText, linksList]);
        content.appendChild(linksSection);
        return content;
    }

    function createCombinedInfoButton() {
        const button = createButton('info_button', USERSCRIPT_STRINGS.infoButton, null);
        button.classList.add('footer-actions');
        button.style.background = 'transparent';
        button.style.border = 'none';
        button.style.boxShadow = 'none';
        button.style.fontFamily = '"Quicksand", sans-serif';
        button.style.color = 'gold';
        button.addEventListener('click', (e) => {
            e.stopPropagation();
            const tabsConfig = [{
                id: 'about',
                title: 'About & FAQ',
                contentGenerator: createAboutTabContent
            }, {
                id: 'links',
                title: 'Useful Links',
                contentGenerator: createLinksTabContent
            }];
            const modalContent = createTabbedModalContent(tabsConfig);
            showAlert({
                title: 'MZ Tactics Manager Info',
                htmlContent: modalContent,
                confirmButtonText: DEFAULT_MODAL_STRINGS.ok
            });
        });
        return button;
    }

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

    function createLinksList(linksMap) {
        const list = document.createElement('ul');
        linksMap.forEach((href, text) => {
            const listItem = document.createElement('li');
            const anchor = document.createElement('a');
            anchor.href = href;
            anchor.target = '_blank';
            anchor.rel = 'noopener noreferrer';
            anchor.textContent = text;
            listItem.appendChild(anchor);
            list.appendChild(listItem);
        });
        return list;
    }

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

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

    async function initializeScriptData() {
        loadCategories();
        await checkVersion();
        const ids = await fetchTeamIdAndUsername();
        if (!ids.teamId) {
            console.warn("MZTM: Failed to get Team ID.");
        }
        let tacticData = GM_getValue(FORMATIONS_STORAGE_KEY);
        const oldTacticData = GM_getValue(OLD_FORMATIONS_STORAGE_KEY);
        if (!tacticData && oldTacticData && oldTacticData.tactics && Array.isArray(oldTacticData.tactics)) {
            console.log(`MZTM: Migrating tactics from old storage key '${OLD_FORMATIONS_STORAGE_KEY}' to '${FORMATIONS_STORAGE_KEY}'.`);
            tacticData = oldTacticData;
            tacticData.tactics = tacticData.tactics.filter(t => t && t.name && t.id && Array.isArray(t.coordinates));
            tacticData.tactics.forEach(t => {
                if (!t.hasOwnProperty('style')) {
                    t.style = OTHER_CATEGORY_ID;
                }
            });
            GM_setValue(FORMATIONS_STORAGE_KEY, tacticData);
            GM_deleteValue(OLD_FORMATIONS_STORAGE_KEY);
            console.log(`MZTM: Migration complete. Deleted old key '${OLD_FORMATIONS_STORAGE_KEY}'.`);
        } else if (!tacticData) {
            console.log("MZTM: No existing tactics data found. Initializing empty store.");
            tacticData = {
                tactics: []
            };
            GM_setValue(FORMATIONS_STORAGE_KEY, tacticData);
        } else {
            if (!tacticData.tactics || !Array.isArray(tacticData.tactics)) tacticData.tactics = [];
            tacticData.tactics = tacticData.tactics.filter(t => t && t.name && t.id && Array.isArray(t.coordinates));
            tacticData.tactics.forEach(t => {
                if (!t.hasOwnProperty('style')) t.style = OTHER_CATEGORY_ID;
            });
        }
        tactics = tacticData.tactics || [];
        tactics.sort((a, b) => a.name.localeCompare(b.name));
        loadCompleteTacticsData();
    }

    function setUpTacticsInterface(mainContainer) {
        const toggleButton = mainContainer.querySelector('#toggle_panel_btn');
        const collapsedIcon = collapsedIconElement || createCollapsedIcon();
        let isCollapsed = GM_getValue(COLLAPSED_KEY, false);
        const anchorButtonId = 'replace-player-btn';
        const applyCollapseState = (instant = false) => {
            const anchorButton = document.getElementById(anchorButtonId);
            if (collapsedIcon && collapsedIcon.parentNode) {
                collapsedIcon.parentNode.removeChild(collapsedIcon);
            }
            if (isCollapsed) {
                if (instant) {
                    mainContainer.style.transition = 'none';
                    mainContainer.classList.add('collapsed');
                    void mainContainer.offsetHeight;
                    mainContainer.style.transition = '';
                } else {
                    mainContainer.classList.add('collapsed');
                }
                toggleButton.innerHTML = '☰';
                toggleButton.title = 'Show panel';
                if (anchorButton) {
                    insertAfterElement(collapsedIcon, anchorButton);
                    collapsedIcon.classList.add('visible');
                } else {
                    console.warn(`MZTM: Anchor button #${anchorButtonId} not found for collapsed icon.`);
                    collapsedIcon.classList.remove('visible');
                }
            } else {
                mainContainer.classList.remove('collapsed');
                toggleButton.innerHTML = '✕';
                toggleButton.title = 'Hide panel';
                collapsedIcon.classList.remove('visible');
            }
        };
        applyCollapseState(true);

        function togglePanel() {
            isCollapsed = !isCollapsed;
            GM_setValue(COLLAPSED_KEY, isCollapsed);
            applyCollapseState();
        }
        toggleButton.addEventListener('click', (e) => {
            e.stopPropagation();
            togglePanel();
        });
        collapsedIcon.addEventListener('click', () => {
            togglePanel();
        });
    }

    async function initialize() {
        const tacticsBox = document.getElementById('tactics_box');
        if (!tacticsBox || !isFootball()) {
            console.log("MZTM: Not on valid page or tactics box not found.");
            return;
        }
        const cachedUserInfo = GM_getValue(USER_INFO_CACHE_KEY);
        if (cachedUserInfo && typeof cachedUserInfo === 'object' && cachedUserInfo.teamId && cachedUserInfo.username && cachedUserInfo.timestamp) {
            userInfoCache = cachedUserInfo;
            if (Date.now() - userInfoCache.timestamp < USER_INFO_CACHE_DURATION_MS) {
                teamId = userInfoCache.teamId;
                username = userInfoCache.username;
            }
        }
        const cachedRoster = GM_getValue(ROSTER_CACHE_KEY);
        if (cachedRoster && typeof cachedRoster === 'object' && cachedRoster.data && cachedRoster.timestamp) {
            rosterCache = cachedRoster;
        }
        try {
            collapsedIconElement = createCollapsedIcon();
            await initializeScriptData();
            const mainContainer = createMainContainer();
            setUpTacticsInterface(mainContainer);
            insertAfterElement(mainContainer, tacticsBox);
            updateTacticsDropdown();
            updateFilterTabs();
            updateCompleteTacticsDropdown();
            const savedMode = GM_getValue(VIEW_MODE_KEY, 'normal');
            setViewMode(savedMode);
        } catch (error) {
            console.error('MZTM Initialization Error:', error);
            const errorDiv = document.createElement('div');
            errorDiv.textContent = 'Error initializing MZ Tactics Manager. Check console for details.';
            errorDiv.style.cssText = 'color:red; padding:10px; border:1px solid red; margin:10px;';
            insertAfterElement(errorDiv, tacticsBox);
        }
    }

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