MZ Tactics Manager

Userscript to manage tactics in ManagerZone

// ==UserScript==
// @name          MZ Tactics Manager
// @namespace     douglaskampl
// @version       13.2.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/mirassol.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 MAX_CATEGORY_NAME_LENGTH = 30;
    const MAX_DESCRIPTION_LENGTH = 250;
    const SCRIPT_VERSION = '13.2.0';
    const DISPLAY_VERSION = '13.2';
    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: 'Update Coords',
        clearButton: 'Clear',
        resetButton: 'Reset',
        importButton: 'Import',
        exportButton: 'Export',
        infoButton: 'FAQ',
        saveButton: 'Save',
        tacticNamePrompt: 'Please enter a name and a category',
        addAlert: 'Formation {} added successfully.',
        deleteAlert: 'Item {} deleted successfully.',
        renameAlert: 'Item {} successfully edited.',
        updateAlert: 'Formation {} updated successfully.',
        clearAlert: 'Formations cleared successfully.',
        resetAlert: 'Default formations reset successfully.',
        importAlert: 'Formations imported successfully.',
        exportAlert: 'Formations 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 formations?',
        resetConfirmation: 'Reset to default formations? This will remove all your custom formations.',
        invalidTacticError: 'Invalid formation. 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 formation 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 Formation',
        completeTacticsDropdownMenuLabel: '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 Formation XML here',
        tacticNamePlaceholder: 'Formation name',
        managerTitle: SCRIPT_NAME,
        searchPlaceholder: 'Search...',
        allTacticsFilter: 'All',
        noTacticsFound: 'No formations found',
        welcomeMessage: `Welcome to ${SCRIPT_NAME} v${DISPLAY_VERSION}!\n\nNew features:\n• Category filter is now a dropdown menu.\n• New Management Modal: Edit/remove formations & add/remove categories via the gear icon (⚙️).\n• Preview on Hover: See formation details by hovering over names in the dropdown.\n\nEnjoy!`,
        welcomeGotIt: 'Got it!',
        removeCategoryConfirmation: 'Remove category "{}"? (All formations in this category will be moved to "Other").',
        removeCategoryAlert: 'Category "{}" removed successfully.',
        removeCategoryButton: 'Remove',
        completeTacticsTitle: 'Tactics Management',
        saveCompleteTacticButton: 'Save Current',
        loadCompleteTacticButton: 'Load',
        deleteCompleteTacticButton: 'Delete',
        renameCompleteTacticButton: 'Rename',
        updateCompleteTacticButton: 'Update with Current',
        importCompleteTacticsButton: 'Import',
        exportCompleteTacticsButton: 'Export',
        completeTacticNamePrompt: 'Please enter a name for the tactic',
        renameCompleteTacticPrompt: 'Enter a new name for the tactic:',
        updateCompleteTacticConfirmation: 'Overwrite tactic "{}" with the current setup (positions, rules, settings) from the pitch?',
        completeTacticSaveSuccess: 'Tactic {} saved successfully.',
        completeTacticLoadSuccess: 'Tactic {} loaded successfully.',
        completeTacticDeleteSuccess: 'Tactic {} deleted successfully.',
        completeTacticRenameSuccess: 'Tactic renamed to {} successfully.',
        completeTacticUpdateSuccess: 'Tactic {} updated successfully.',
        importCompleteTacticsTitle: 'Import Tactics (JSON)',
        exportCompleteTacticsTitle: 'Export Tactics (JSON)',
        importCompleteTacticsPlaceholder: 'Paste Tactics JSON here',
        importCompleteTacticsAlert: 'Tactics imported successfully.',
        exportCompleteTacticsAlert: 'Tactics JSON copied to clipboard.',
        invalidCompleteImportError: 'Invalid import data. Please provide valid JSON (object map).',
        errorFetchingRoster: 'Error fetching team roster. Cannot load Tactic.',
        errorInsufficientPlayers: '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 were players replaced at random. Tactic updated!',
        invalidXmlForImport: 'MZ rejected the generated XML. It might be invalid or player assignments failed.',
        completeTacticNamePlaceholder: 'Tactic name',
        normalModeLabel: 'Formations',
        completeModeLabel: 'Tactics',
        modeLabel: '',
        manageCategoriesTitle: 'Manage Categories',
        noCustomCategories: 'No custom categories to manage.',
        manageCategoriesDoneButton: 'Done',
        managementModalTitle: 'Manage Formations & Categories',
        formationsTabTitle: 'Formations',
        categoriesTabTitle: 'Categories',
        addCategoryPlaceholder: 'New category name...',
        addCategoryButton: '+ Add',
        categoryNameMaxLengthError: `Category name too long (max ${MAX_CATEGORY_NAME_LENGTH} chars).`,
        saveChangesButton: 'Save Changes',
        changesSavedSuccess: 'Changes saved successfully.',
        noChangesToSave: 'No changes to save.',
        descriptionLabel: 'Description (optional):',
        descriptionPlaceholder: `Enter a short description (max ${MAX_DESCRIPTION_LENGTH} chars)...`,
        descriptionMaxLengthError: `Description too long (max ${MAX_DESCRIPTION_LENGTH} chars).`,
        previewFormationLabel: 'Formation:',
        xmlRequiredError: 'Please paste the XML data first.',
        invalidXmlFormatError: 'The provided text does not appear to be valid XML.',
        noTacticsSaved: 'No formations saved',
        noCompleteTacticsSaved: 'No tactics saved'
    };
    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;
    let previewElement = null;
    let previewHideTimeout = null;
    let currentOpenDropdown = null;
    let selectedFormationTacticId = null;
    let selectedCompleteTacticName = 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 = inputElement.tagName === 'TEXTAREA' ? '5px' : '-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, descElement, categorySelect, newCategoryInput, overlayElement, resolve) {
        if (options.input === 'text' && options.inputValidator && inputElement) {
            const validationError = validateModalInput(inputElement, options.inputValidator, 'mz-modal-input-error');
            if (validationError) return;
        }
        if (options.descriptionInput === 'textarea' && options.descriptionValidator && descElement) {
            const descValidationError = validateModalInput(descElement, options.descriptionValidator, 'mz-modal-desc-error');
            if (descValidationError) 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 (options.descriptionInput === 'textarea') {
                result.description = descElement ? descElement.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,
                description: null
            });
        });
    }

    function setUpKeyboardHandler(confirmHandler, cancelHandler, inputElement, descElement) {
        return function(event) {
            if (event.key === 'Escape') {
                cancelHandler();
            } else if (event.key === 'Enter') {
                const activeEl = document.activeElement;
                if (!(activeEl === descElement && descElement?.tagName === 'TEXTAREA') && !(activeEl === inputElement && inputElement?.tagName === 'TEXTAREA')) {
                    confirmHandler();
                } else if (activeEl === inputElement && inputElement?.tagName === 'INPUT') {
                    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';
            if (options.modalClass) container.classList.add(options.modalClass);
            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,
                descElem = null,
                descLabel = 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.descriptionInput === 'textarea') {
                descLabel = document.createElement('label');
                descLabel.className = 'mz-modal-label';
                descLabel.textContent = options.descriptionLabel || USERSCRIPT_STRINGS.descriptionLabel;
                descLabel.htmlFor = 'mz-modal-description';
                descElem = document.createElement('textarea');
                descElem.id = 'mz-modal-description';
                descElem.value = options.descriptionValue || '';
                descElem.placeholder = options.descriptionPlaceholder || USERSCRIPT_STRINGS.descriptionPlaceholder;
                descElem.rows = 3;
            }

            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, descElem, 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, descElem);
            document.addEventListener('keydown', keyboardHandler);

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

            overlay.appendChild(container);
            document.body.appendChild(overlay);
            setTimeout(() => {
                overlay.classList.add('active');
                if (inputElem) inputElem.focus();
                else if (descElem) descElem.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(tacticId) {
        selectedFormationTacticId = tacticId;
        if (!tacticId) return;
        const outfieldPlayers = Array.from(document.querySelectorAll(OUTFIELD_PLAYERS_SELECTOR));
        const selectedTactic = tactics.find(td => td.id === tacticId);
        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 getFormationFromCompleteTactic(tacticData) {
        let strikers = 0,
            midfielders = 0,
            defenders = 0;
        const outfieldCoords = tacticData.initialCoords.filter(p => p.pos === 'normal');
        for (const coord of outfieldCoords) {
            const y = coord.y;
            const effectiveY = y - 9;
            if (effectiveY < 103) strikers++;
            else if (effectiveY <= 204) midfielders++;
            else defenders++;
        }
        if (strikers + midfielders + defenders !== 10) {
            console.warn("MZTM: Calculated formation from complete tactic doesn't sum to 10 outfield players.");
        }
        return {
            strikers,
            midfielders,
            defenders
        };
    }

    function formatFormationString(formationObj) {
        if (!formationObj || typeof formationObj.defenders === 'undefined') return 'N/A';
        return `${formationObj.defenders}-${formationObj.midfielders}-${formationObj.strikers}`;
    }

    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, sourceModalElement = null) {
        if (!categoryId || categoryId === 'all' || categoryId === OTHER_CATEGORY_ID || DEFAULT_CATEGORIES[categoryId]) {
            console.error("Cannot remove this category:", categoryId);
            return false;
        }

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

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

        delete categories[categoryId];
        saveCategories();

        if (currentFilter === categoryId) currentFilter = 'all';

        updateTacticsDropdown();
        updateCategoryFilterDropdown();

        if (sourceModalElement) {
            const categoryItem = sourceModalElement.querySelector(`li[data-category-id="${categoryId}"]`);
            if (categoryItem) {
                categoryItem.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
                categoryItem.style.opacity = '0';
                categoryItem.style.transform = 'translateX(-20px)';
                setTimeout(() => categoryItem.remove(), 300);
            }
        }

        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.removeCategoryAlert.replace('{}', categoryName));
        return true;
    }

    async function showManagementModal() {
        const modalContent = createManagementModalContent();
        await showAlert({
            title: USERSCRIPT_STRINGS.managementModalTitle,
            htmlContent: modalContent,
            confirmButtonText: USERSCRIPT_STRINGS.manageCategoriesDoneButton,
            showCancelButton: false,
            modalClass: 'management-modal'
        });
        updateCategoryFilterDropdown();
        updateTacticsDropdown();
    }

    function createManagementModalContent() {
        const wrapper = document.createElement('div');
        wrapper.className = 'management-modal-wrapper';

        const tabsConfig = [
            { id: 'formations', title: USERSCRIPT_STRINGS.formationsTabTitle, contentGenerator: createFormationsManagementTab },
            { id: 'categories', title: USERSCRIPT_STRINGS.categoriesTabTitle, contentGenerator: createCategoriesManagementTab }
        ];

        const tabsContainer = createModalTabs(tabsConfig, wrapper);
        wrapper.appendChild(tabsContainer);

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

        wrapper.addEventListener('click', handleManagementModalClick);
        wrapper.addEventListener('change', handleManagementModalChange);
        wrapper.addEventListener('keydown', handleManagementModalKeydown);

        return wrapper;
    }

    function createFormationsManagementTab(container) {
        container.innerHTML = '';
        const list = document.createElement('ul');
        list.className = 'formation-management-list';

        const sortedTactics = [...tactics].sort((a, b) => a.name.localeCompare(b.name));

        if (sortedTactics.length === 0) {
            const message = document.createElement('p');
            message.textContent = 'No formations saved yet.';
            message.className = 'no-items-message';
            list.appendChild(message);
        } else {
            sortedTactics.forEach(tactic => {
                list.appendChild(createFormationManagementItem(tactic));
            });
        }
        container.appendChild(list);
    }

    function createFormationManagementItem(tactic) {
        const listItem = document.createElement('li');
        listItem.dataset.tacticId = tactic.id;

        const nameContainer = document.createElement('div');
        nameContainer.className = 'item-name-container';
        const nameSpan = document.createElement('span');
        nameSpan.className = 'item-name';
        nameSpan.textContent = tactic.name;
        nameContainer.appendChild(nameSpan);

        const controlsContainer = document.createElement('div');
        controlsContainer.className = 'item-controls';

        const categorySelect = document.createElement('select');
        categorySelect.className = 'item-category-select';
        populateCategorySelect(categorySelect, tactic.style);

        const editBtn = document.createElement('button');
        editBtn.className = 'item-action-btn edit-name-btn';
        editBtn.innerHTML = '✏️';
        editBtn.title = 'Edit name & description';

        const deleteBtn = document.createElement('button');
        deleteBtn.className = 'item-action-btn delete-item-btn';
        deleteBtn.innerHTML = '🗑️';
        deleteBtn.title = 'Delete formation';

        appendChildren(controlsContainer, [categorySelect, editBtn, deleteBtn]);
        appendChildren(listItem, [nameContainer, controlsContainer]);

        return listItem;
    }

    function populateCategorySelect(selectElement, currentCategoryId) {
        selectElement.innerHTML = '';
        const availableCategories = Object.values(categories)
        .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 => {
            const option = document.createElement('option');
            option.value = cat.id;
            option.textContent = cat.name;
            if (cat.id === (currentCategoryId || OTHER_CATEGORY_ID)) {
                option.selected = true;
            }
            selectElement.appendChild(option);
        });
    }

    function createCategoriesManagementTab(container) {
        container.innerHTML = '';

        const addCategorySection = document.createElement('div');
        addCategorySection.className = 'add-category-section';
        const newCategoryInput = document.createElement('input');
        newCategoryInput.type = 'text';
        newCategoryInput.placeholder = USERSCRIPT_STRINGS.addCategoryPlaceholder;
        newCategoryInput.className = 'add-category-input';
        const addCategoryBtn = document.createElement('button');
        addCategoryBtn.className = 'mz-modal-btn add-category-btn';
        addCategoryBtn.textContent = USERSCRIPT_STRINGS.addCategoryButton;
        appendChildren(addCategorySection, [newCategoryInput, addCategoryBtn]);
        container.appendChild(addCategorySection);

        const list = document.createElement('ul');
        list.className = 'category-management-list';

        const customCategories = Object.values(categories)
        .filter(cat => cat.id !== OTHER_CATEGORY_ID && !DEFAULT_CATEGORIES[cat.id])
        .sort((a, b) => a.name.localeCompare(b.name));

        const noCatMsg = document.createElement('p');
        noCatMsg.textContent = USERSCRIPT_STRINGS.noCustomCategories;
        noCatMsg.className = 'no-custom-categories-message';
        noCatMsg.style.display = customCategories.length === 0 ? 'block' : 'none';
        list.appendChild(noCatMsg);

        customCategories.forEach(cat => {
            list.appendChild(createCategoryManagementItem(cat));
        });

        container.appendChild(list);
    }

    function createCategoryManagementItem(category) {
        const listItem = document.createElement('li');
        listItem.dataset.categoryId = category.id;

        const nameSpan = document.createElement('span');
        nameSpan.textContent = category.name;
        nameSpan.style.flexGrow = '1';
        nameSpan.style.marginRight = '10px';

        const removeBtn = document.createElement('button');
        removeBtn.textContent = 'Remove';
        removeBtn.className = 'mz-modal-btn category-remove-btn';
        removeBtn.title = `Remove category "${category.name}"`;

        listItem.appendChild(nameSpan);
        listItem.appendChild(removeBtn);
        return listItem;
    }

    function handleManagementModalClick(event) {
        const target = event.target;
        const listItem = target.closest('li');

        if (target.classList.contains('edit-name-btn') && listItem) {
            handleEditFormationInModal(listItem);
        } else if (target.classList.contains('delete-item-btn') && listItem) {
            handleDeleteFormationInModal(listItem);
        } else if (target.classList.contains('add-category-btn')) {
            handleAddNewCategoryInModal(target.closest('.add-category-section'));
        } else if (target.classList.contains('category-remove-btn') && listItem) {
            handleDeleteCategoryInModal(listItem);
        }
    }

    function handleManagementModalChange(event) {
        const target = event.target;
        if (target.classList.contains('item-category-select')) {
            const listItem = target.closest('li');
            const tacticId = listItem?.dataset.tacticId;
            const newCategoryId = target.value;
            if (tacticId && newCategoryId) {
                updateFormationCategory(tacticId, newCategoryId);
            }
        }
    }

    function handleManagementModalKeydown(event) {
        if (event.key === 'Enter' && !event.shiftKey) {
            const target = event.target;
            if (target.classList.contains('add-category-input')) {
                handleAddNewCategoryInModal(target.closest('.add-category-section'));
                event.preventDefault();
            }
        }
    }

    async function handleEditFormationInModal(listItem) {
        const tacticId = listItem.dataset.tacticId;
        const tactic = tactics.find(t => t.id === tacticId);
        if (!tactic) return;

        await editTactic(tactic.id, listItem);
    }

    async function handleDeleteFormationInModal(listItem) {
        const tacticId = listItem.dataset.tacticId;
        const tactic = tactics.find(t => t.id === tacticId);
        if (!tactic) return;

        const confirmation = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle,
            text: USERSCRIPT_STRINGS.deleteConfirmation.replace('{}', tactic.name),
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.deleteTacticConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton,
            type: 'error'
        });

        if (!confirmation.isConfirmed) return;

        const deletedCategoryId = tactic.style;
        const data = await GM_getValue(FORMATIONS_STORAGE_KEY) || { tactics: [] };
        data.tactics = data.tactics.filter(t => t.id !== tacticId);
        await GM_setValue(FORMATIONS_STORAGE_KEY, data);
        tactics = tactics.filter(t => t.id !== tacticId);

        listItem.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
        listItem.style.opacity = '0';
        listItem.style.transform = 'translateX(-20px)';
        setTimeout(() => {
            listItem.remove();
            const list = document.querySelector('.formation-management-list');
            if (list && !list.querySelector('li')) {
                const message = document.createElement('p');
                message.textContent = 'No formations saved yet.';
                message.className = 'no-items-message';
                list.appendChild(message);
            }
        }, 300);

        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';
            const catTab = document.querySelector('.management-modal-content[data-tab-id="categories"]');
            if (catTab) createCategoriesManagementTab(catTab);
        }

        updateTacticsDropdown();
        updateCategoryFilterDropdown();
    }

    async function updateFormationCategory(tacticId, newCategoryId) {
        const tacticIndex = tactics.findIndex(t => t.id === tacticId);
        if (tacticIndex === -1) return;

        const originalCategoryId = tactics[tacticIndex].style;
        if (originalCategoryId === newCategoryId) return;

        tactics[tacticIndex].style = newCategoryId;
        const data = await GM_getValue(FORMATIONS_STORAGE_KEY, { tactics: [] });
        const dataIndex = data.tactics.findIndex(t => t.id === tacticId);
        if (dataIndex !== -1) {
            data.tactics[dataIndex].style = newCategoryId;
            await GM_setValue(FORMATIONS_STORAGE_KEY, data);

            const originalCategoryStillUsed = tactics.some(t => t.style === originalCategoryId);
            if (!originalCategoryStillUsed && originalCategoryId && !DEFAULT_CATEGORIES[originalCategoryId] && originalCategoryId !== OTHER_CATEGORY_ID) {
                delete categories[originalCategoryId];
                saveCategories();
                if (currentFilter === originalCategoryId) currentFilter = 'all';
                const catTab = document.querySelector('.management-modal-content[data-tab-id="categories"]');
                if (catTab) createCategoriesManagementTab(catTab);
            }

            updateTacticsDropdown();
            updateCategoryFilterDropdown();
        } else {
            showErrorMessage(USERSCRIPT_STRINGS.errorTitle, "Failed to update category in storage.");
            tactics[tacticIndex].style = originalCategoryId;
            const selectElement = document.querySelector(`li[data-tactic-id="${tacticId}"] .item-category-select`);
            if (selectElement) selectElement.value = originalCategoryId || OTHER_CATEGORY_ID;
        }
    }

    async function handleAddNewCategoryInModal(addSection) {
        const input = addSection.querySelector('.add-category-input');
        const list = addSection.nextElementSibling;
        if (!input || !list) return;

        const name = input.value.trim();
        if (!name) {
            showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticNameProvidedError.replace("name", "category name"));
            input.focus();
            return;
        }
        if (name.length > MAX_CATEGORY_NAME_LENGTH) {
            showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.categoryNameMaxLengthError);
            input.focus();
            return;
        }
        const existingCategory = Object.values(categories).find(cat => cat.name.toLowerCase() === name.toLowerCase());
        if (existingCategory) {
            showErrorMessage(USERSCRIPT_STRINGS.errorTitle, "Category name already exists.");
            input.focus();
            return;
        }

        const newCategoryId = generateCategoryId(name);
        const newCategory = {
            id: newCategoryId,
            name: name,
            color: generateCategoryColor(name)
        };

        addCategory(newCategory);
        input.value = '';

        const noCatMsg = list.querySelector('.no-custom-categories-message');
        if (noCatMsg) noCatMsg.style.display = 'none';

        const newItem = createCategoryManagementItem(newCategory);
        list.appendChild(newItem);
        newItem.style.opacity = '0';
        newItem.style.transform = 'translateY(-10px)';
        setTimeout(() => {
            newItem.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
            newItem.style.opacity = '1';
            newItem.style.transform = 'translateY(0)';
        }, 10);

        updateCategoryFilterDropdown();
        document.querySelectorAll('.item-category-select').forEach(select => {
            const currentTacticId = select.closest('li')?.dataset.tacticId;
            const currentTactic = tactics.find(t => t.id === currentTacticId);
            populateCategorySelect(select, currentTactic?.style);
        });
    }

    async function handleDeleteCategoryInModal(listItem) {
        const categoryId = listItem.dataset.categoryId;
        const categoryName = getCategoryName(categoryId);
        if (!categoryId || !categoryName) return;

        const success = await removeCategory(categoryId, listItem.closest('.management-modal-content'));
        if (success) {
            document.querySelectorAll('.item-category-select').forEach(select => {
                const currentTacticId = select.closest('li')?.dataset.tacticId;
                const currentTactic = tactics.find(t => t.id === currentTacticId);
                populateCategorySelect(select, currentTactic?.style);
            });
            const formationsTab = document.querySelector('.management-modal-content[data-tab-id="formations"]');
            if (formationsTab) createFormationsManagementTab(formationsTab);
        }
    }

    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;
            },
            descriptionInput: 'textarea',
            descriptionValue: '',
            descriptionPlaceholder: USERSCRIPT_STRINGS.descriptionPlaceholder,
            descriptionValidator: (d) => {
                if (d && d.length > MAX_DESCRIPTION_LENGTH) return USERSCRIPT_STRINGS.descriptionMaxLengthError;
                return null;
            },
            showCategorySelector: true,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.saveButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!result.isConfirmed || !result.value) return;
        const name = result.value;
        const description = result.description || '';
        const categoryId = result.category.id;
        const tactic = {
            name: name,
            description: description,
            coordinates: coordinates,
            id: id,
            style: categoryId
        };
        await saveTacticToStorage(tactic);
        tactics.push(tactic);
        tactics.sort((a, b) => a.name.localeCompare(b.name));
        updateTacticsDropdown(id);
        updateCategoryFilterDropdown();
        handleTacticSelection(tactic.id);
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.addAlert.replace('{}', tactic.name));
    }

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

        if (!xmlResult.isConfirmed) return;
        const xml = xmlResult.value;

        if (!xml) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.xmlRequiredError);
            return;
        }
        if (!xml.trim().startsWith('<') || !xml.trim().endsWith('>')) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.invalidXmlFormatError);
            return;
        }

        const nameResult = 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;
            },
            descriptionInput: 'textarea',
            descriptionValue: '',
            descriptionPlaceholder: USERSCRIPT_STRINGS.descriptionPlaceholder,
            descriptionValidator: (d) => {
                if (d && d.length > MAX_DESCRIPTION_LENGTH) return USERSCRIPT_STRINGS.descriptionMaxLengthError;
                return null;
            },
            showCategorySelector: true,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.saveButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!nameResult.isConfirmed || !nameResult.value) return;
        const name = nameResult.value;
        const description = nameResult.description || '';
        const categoryId = nameResult.category.id;
        try {
            const newTactic = await convertXmlToSimpleFormationJson(xml, name);
            newTactic.style = categoryId;
            newTactic.description = description;
            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(id);
            updateCategoryFilterDropdown();
            handleTacticSelection(newTactic.id);
            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 selectedTactic = tactics.find(t => t.id === selectedFormationTacticId);
        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);
        selectedFormationTacticId = null;
        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();
        updateCategoryFilterDropdown();
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.deleteAlert.replace('{}', selectedTactic.name));
    }

    async function editTactic(tacticIdToEdit = null, sourceListItem = null) {
        const idToUse = tacticIdToEdit || selectedFormationTacticId;
        const selectedTactic = tactics.find(t => t.id === idToUse);

        if (!selectedTactic) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
            return;
        }

        const originalName = selectedTactic.name;
        const originalCategory = selectedTactic.style;
        const originalDescription = selectedTactic.description || '';

        const result = await showAlert({
            title: 'Edit Formation',
            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;
            },
            descriptionInput: 'textarea',
            descriptionValue: originalDescription,
            descriptionPlaceholder: USERSCRIPT_STRINGS.descriptionPlaceholder,
            descriptionValidator: (d) => {
                if (d && d.length > MAX_DESCRIPTION_LENGTH) return USERSCRIPT_STRINGS.descriptionMaxLengthError;
                return null;
            },
            showCategorySelector: true,
            currentCategory: selectedTactic.style,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.saveButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });

        if (!result.isConfirmed) return;

        const newName = result.value || originalName;
        const newDescription = result.description || '';
        const newCategory = result.category?.id || originalCategory;

        if (newName === originalName && newCategory === originalCategory && newDescription === originalDescription) {
            return;
        }

        const categoryChanged = originalCategory !== newCategory;
        const data = await GM_getValue(FORMATIONS_STORAGE_KEY) || {
            tactics: []
        };

        let updatedInStorage = false;
        data.tactics = data.tactics.map(t => {
            if (t.id === selectedTactic.id) {
                t.name = newName;
                t.style = newCategory;
                t.description = newDescription;
                updatedInStorage = true;
            }
            return t;
        });

        if (!updatedInStorage) {
            console.error("MZTM: Failed to find tactic in storage for update.", selectedTactic.id);
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, "Failed to update tactic in storage.");
            return;
        }

        await GM_setValue(FORMATIONS_STORAGE_KEY, data);

        const tacticIndex = tactics.findIndex(t => t.id === selectedTactic.id);
        if (tacticIndex !== -1) {
            tactics[tacticIndex].name = newName;
            tactics[tacticIndex].style = newCategory;
            tactics[tacticIndex].description = newDescription;
        } else {
            console.error("MZTM: Failed to find tactic in memory for update.", selectedTactic.id);
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, "Internal error updating tactic data.");
            await initializeScriptData();
            updateTacticsDropdown();
            updateCategoryFilterDropdown();
            return;
        }

        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(selectedTactic.id);
        updateCategoryFilterDropdown();

        if (sourceListItem) {
            const nameSpan = sourceListItem.querySelector('.item-name');
            if (nameSpan) nameSpan.textContent = newName;
            const categorySelect = sourceListItem.querySelector('.item-category-select');
            if (categorySelect) categorySelect.value = newCategory;
        }

        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.renameAlert.replace('{}', newName));
    }

    async function updateTactic() {
        const outfieldPlayers = Array.from(document.querySelectorAll(OUTFIELD_PLAYERS_SELECTOR));
        const selectedTactic = tactics.find(t => t.id === selectedFormationTacticId);
        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);
        selectedFormationTacticId = newId;
        updateTacticsDropdown(newId);
        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 = [];
        selectedFormationTacticId = null;
        currentFilter = 'all';
        loadCategories();
        updateTacticsDropdown();
        updateCategoryFilterDropdown();
        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 = [];
        selectedFormationTacticId = null;
        currentFilter = 'all';
        loadCategories();
        updateTacticsDropdown();
        updateCategoryFilterDropdown();
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.resetAlert);
    }

    async function importTacticsJsonData() {
        try {
            const result = await showAlert({
                title: 'Import Formations (JSON)',
                input: 'text',
                inputValue: '',
                placeholder: 'Paste Formations 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.hasOwnProperty('description')) t.description = '';
                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++;
                } else {
                    const existingIndex = mergedTactics.findIndex(t => t.id === impTactic.id);
                    if (existingIndex !== -1) {
                        mergedTactics[existingIndex] = { ...mergedTactics[existingIndex], ...impTactic };
                    }
                }
            }
            await GM_setValue(FORMATIONS_STORAGE_KEY, {
                tactics: mergedTactics
            });
            mergedTactics.sort((a, b) => a.name.localeCompare(b.name));
            tactics = mergedTactics;
            updateTacticsDropdown();
            updateCategoryFilterDropdown();
            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 Formations (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 formations.');
        }
    }

    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(),
            description: ''
        };
        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,
            inputValidator: (v) => {
                if (!v) return USERSCRIPT_STRINGS.noTacticNameProvidedError;
                if (v.length > MAX_TACTIC_NAME_LENGTH) return USERSCRIPT_STRINGS.tacticNameMaxLengthError;
                if (completeTactics.hasOwnProperty(v)) return USERSCRIPT_STRINGS.alreadyExistingTacticNameError;
                return null;
            },
            descriptionInput: 'textarea',
            descriptionValue: '',
            descriptionPlaceholder: USERSCRIPT_STRINGS.descriptionPlaceholder,
            descriptionValidator: (d) => {
                if (d && d.length > MAX_DESCRIPTION_LENGTH) return USERSCRIPT_STRINGS.descriptionMaxLengthError;
                return null;
            },
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.saveButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton,
        });
        if (!result.isConfirmed || !result.value) {
            if (windowHidden) document.getElementById('close_button')?.click();
            return;
        }
        const baseName = result.value;
        const description = result.description || '';
        const fullName = `${baseName} (${getFormattedDate()})`;
        savedData.description = description;
        completeTactics[fullName] = savedData;
        saveCompleteTacticsData();
        updateCompleteTacticsDropdown(fullName);
        if (windowHidden) document.getElementById('close_button')?.click();
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.completeTacticSaveSuccess.replace('{}', fullName));
    }

    async function loadCompleteTactic() {
        const selectedName = selectedCompleteTacticName;
        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}` : ''));

            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 selectedName = selectedCompleteTacticName;
        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];
        selectedCompleteTacticName = null;
        saveCompleteTacticsData();
        updateCompleteTacticsDropdown();
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.completeTacticDeleteSuccess.replace('{}', selectedName));
    }

    async function editCompleteTactic() {
        const selectedName = selectedCompleteTacticName;
        if (!selectedName || !completeTactics[selectedName]) {
            return showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
        }
        const originalTacticData = completeTactics[selectedName];
        const originalDescription = originalTacticData.description || '';

        const result = await showAlert({
            title: USERSCRIPT_STRINGS.renameCompleteTacticPrompt,
            input: 'text',
            inputValue: selectedName,
            placeholder: USERSCRIPT_STRINGS.completeTacticNamePlaceholder,
            inputValidator: (v) => {
                if (!v) return USERSCRIPT_STRINGS.noTacticNameProvidedError;
                if (v.length > MAX_TACTIC_NAME_LENGTH) return USERSCRIPT_STRINGS.tacticNameMaxLengthError;
                if (v !== selectedName && completeTactics.hasOwnProperty(v)) return USERSCRIPT_STRINGS.alreadyExistingTacticNameError;
                return null;
            },
            descriptionInput: 'textarea',
            descriptionValue: originalDescription,
            descriptionPlaceholder: USERSCRIPT_STRINGS.descriptionPlaceholder,
            descriptionValidator: (d) => {
                if (d && d.length > MAX_DESCRIPTION_LENGTH) return USERSCRIPT_STRINGS.descriptionMaxLengthError;
                return null;
            },
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.saveButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });

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

        const newName = result.value;
        const newDescription = result.description || '';

        if (newName === selectedName && newDescription === originalDescription) return;

        const tacticData = completeTactics[selectedName];
        tacticData.description = newDescription;

        delete completeTactics[selectedName];
        completeTactics[newName] = tacticData;

        saveCompleteTacticsData();
        selectedCompleteTacticName = newName;
        updateCompleteTacticsDropdown(newName);
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.completeTacticRenameSuccess.replace('{}', newName));
    }

    async function updateCompleteTactic() {
        const selectedName = selectedCompleteTacticName;
        if (!selectedName || !completeTactics[selectedName]) {
            return showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
        }

        const confirmation = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle,
            text: USERSCRIPT_STRINGS.updateCompleteTacticConfirmation.replace('{}', selectedName),
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.updateConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });

        if (!confirmation.isConfirmed) return;

        const originalAlert = window.alert;
        try {
            const exportButton = document.getElementById('export_button');
            const importExportWindow = document.getElementById('importExportTacticsWindow');
            const importExportData = document.getElementById('importExportData');
            if (!exportButton || !importExportWindow || !importExportData) throw new Error('Could not find required MZ UI elements for export.');

            const windowHidden = importExportWindow.style.display === 'none';
            if (windowHidden) {
                document.getElementById('import_export_button')?.click();
                await new Promise(r => setTimeout(r, 50));
            }

            importExportData.value = '';
            exportButton.click();
            await new Promise(r => setTimeout(r, 200));
            const xmlString = importExportData.value;
            if (windowHidden) document.getElementById('close_button')?.click();

            if (!xmlString) throw new Error('Export did not produce XML.');

            let updatedData;
            try {
                updatedData = parseCompleteTacticXml(xmlString);
            } catch (error) {
                console.error("XML Parse Error on Update:", error);
                throw new Error(USERSCRIPT_STRINGS.errorXmlExportParse);
            }

            updatedData.description = completeTactics[selectedName]?.description || '';
            completeTactics[selectedName] = updatedData;
            saveCompleteTacticsData();
            updateCompleteTacticsDropdown(selectedName);
            await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.completeTacticUpdateSuccess.replace('{}', selectedName));

        } catch (error) {
            console.error("Update Complete Tactic Error:", error);
            showErrorMessage(USERSCRIPT_STRINGS.errorTitle, error.message || 'Unknown error during update.');
            if (window.alert !== originalAlert) window.alert = originalAlert;
            const importExportWindow = document.getElementById('importExportTacticsWindow');
            if (importExportWindow && importExportWindow.style.display !== 'none'){
                document.getElementById('close_button')?.click();
            }
        }
    }

    async function importCompleteTactics() {
        try {
            const result = await showAlert({
                title: USERSCRIPT_STRINGS.importCompleteTacticsTitle,
                input: 'text',
                inputValue: '',
                placeholder: USERSCRIPT_STRINGS.importCompleteTacticsPlaceholder,
                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.invalidCompleteImportError);
                return;
            }

            if (typeof importedData !== 'object' || importedData === null || Array.isArray(importedData)) {
                await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.invalidCompleteImportError);
                return;
            }

            let addedCount = 0;
            let updatedCount = 0;
            for (const name in importedData) {
                if (importedData.hasOwnProperty(name)) {
                    if (typeof importedData[name] === 'object' && importedData[name] !== null) {
                        if (!importedData[name].hasOwnProperty('description')) importedData[name].description = '';
                        if (!completeTactics.hasOwnProperty(name)) {
                            addedCount++;
                        } else {
                            updatedCount++;
                        }
                        completeTactics[name] = importedData[name];
                    } else {
                        console.warn(`MZTM: Skipping invalid tactic data during import for key: ${name}`);
                    }
                }
            }

            saveCompleteTacticsData();
            updateCompleteTacticsDropdown();
            let message = USERSCRIPT_STRINGS.importCompleteTacticsAlert;
            if(addedCount > 0 || updatedCount > 0) {
                message += ` (${addedCount > 0 ? `${addedCount} new` : ''}${addedCount > 0 && updatedCount > 0 ? ', ' : ''}${updatedCount > 0 ? `${updatedCount} updated` : ''} items)`;
            }
            await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, message);

        } catch (error) {
            console.error('Import Complete Tactics Error:', error);
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.invalidCompleteImportError + (error.message ? `: ${error.message}` : ''));
        }
    }

    async function exportCompleteTactics() {
        try {
            const jsonString = JSON.stringify(completeTactics, null, 2);
            if (navigator.clipboard?.writeText) {
                try {
                    await navigator.clipboard.writeText(jsonString);
                    await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.exportCompleteTacticsAlert);
                    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: USERSCRIPT_STRINGS.exportCompleteTacticsTitle,
                htmlContent: container,
                confirmButtonText: 'Done'
            });
            textArea.select();
            textArea.setSelectionRange(0, 99999);
        } catch (error) {
            console.error('Export Complete Tactics error:', error);
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, 'Failed to export tactics.');
        }
    }

    function createTacticPreviewElement() {
        if (previewElement) return previewElement;
        previewElement = document.createElement('div');
        previewElement.id = 'mztm-tactic-preview';
        previewElement.style.display = 'none';
        previewElement.style.opacity = '0';
        previewElement.addEventListener('mouseenter', () => {
            if(previewHideTimeout) clearTimeout(previewHideTimeout);
        });
        previewElement.addEventListener('mouseleave', hideTacticPreview);
        document.body.appendChild(previewElement);
        return previewElement;
    }

    function updatePreviewPosition(event) {
        if (!previewElement || previewElement.style.display === 'none') return;
        const xOffset = 15;
        const yOffset = 10;
        let x = event.clientX + xOffset;
        let y = event.clientY + yOffset;
        const previewRect = previewElement.getBoundingClientRect();
        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;

        if (x + previewRect.width > viewportWidth - 10) {
            x = event.clientX - previewRect.width - xOffset;
        }
        if (y + previewRect.height > viewportHeight - 10) {
            y = event.clientY - previewRect.height - yOffset;
        }
        if (x < 10) x = 10;
        if (y < 10) y = 10;

        previewElement.style.left = `${x}px`;
        previewElement.style.top = `${y}px`;
    }

    function showTacticPreview(event, listItem) {
        if (!listItem || listItem.classList.contains('mztm-custom-select-category') || listItem.classList.contains('mztm-custom-select-no-results')) {
            hideTacticPreview();
            return;
        }

        if(previewHideTimeout) clearTimeout(previewHideTimeout);

        const tacticId = listItem.dataset.tacticId;
        const tacticName = listItem.dataset.tacticName;
        const description = listItem.dataset.description || '';
        const formationString = listItem.dataset.formationString || 'N/A';
        const isCompleteTactic = listItem.closest('#complete_tactics_selector_list');


        if (!tacticId && !tacticName) {
            hideTacticPreview();
            return;
        }

        const previewDiv = createTacticPreviewElement();
        previewDiv.innerHTML = `
            <div class="mztm-preview-formation"><strong>${USERSCRIPT_STRINGS.previewFormationLabel}</strong> ${formationString}</div>
            ${description ? `<div class="mztm-preview-desc">${description.replace(/\n/g, '<br>')}</div>` : '<div class="mztm-preview-no-desc">No description available.</div>'}
        `;
        previewDiv.style.display = 'block';
        requestAnimationFrame(() => {
            updatePreviewPosition(event);
            previewDiv.style.opacity = '1';
        });

        document.addEventListener('mousemove', updatePreviewPosition);
    }

    function hideTacticPreview() {
        if(previewHideTimeout) clearTimeout(previewHideTimeout);
        previewHideTimeout = setTimeout(() => {
            if (previewElement) {
                previewElement.style.opacity = '0';
                setTimeout(() => {
                    if (previewElement && previewElement.style.opacity === '0') {
                        previewElement.style.display = 'none';
                    }
                }, 200);
                document.removeEventListener('mousemove', updatePreviewPosition);
            }
            previewHideTimeout = null;
        }, 100);
    }

    function addPreviewListenersToList(listElement) {
        if (!listElement) return;

        listElement.addEventListener('mouseover', (event) => {
            const listItem = event.target.closest('.mztm-custom-select-item');
            if (listItem && !listItem.classList.contains('disabled')) {
                showTacticPreview(event, listItem);
            } else if (!listItem) {
                hideTacticPreview();
            }
        });

        listElement.addEventListener('mouseout', (event) => {
            const listItem = event.target.closest('.mztm-custom-select-item');
            if (listItem) {
                const related = event.relatedTarget;
                if (!listItem.contains(related) && related !== previewElement) {
                    hideTacticPreview();
                }
            } else if (!listElement.contains(event.relatedTarget) && (!previewElement || event.relatedTarget !== previewElement)) {
                hideTacticPreview();
            }
        });

        window.addEventListener('scroll', hideTacticPreview, true);
    }

    function closeAllCustomDropdowns(exceptElement = null) {
        document.querySelectorAll('.mztm-custom-select-list-container.open').forEach(container => {
            const wrapper = container.closest('.mztm-custom-select-wrapper');
            if (wrapper !== exceptElement?.closest('.mztm-custom-select-wrapper')) {
                container.classList.remove('open');
                const trigger = wrapper?.querySelector('.mztm-custom-select-trigger');
                trigger?.classList.remove('open');
            }
        });
        currentOpenDropdown = exceptElement?.closest('.mztm-custom-select-wrapper') || null;
    }

    document.addEventListener('click', (event) => {
        if (currentOpenDropdown && !currentOpenDropdown.contains(event.target)) {
            closeAllCustomDropdowns();
        }
    });

    function createCustomSelect(id, placeholderText) {
        const wrapper = document.createElement('div');
        wrapper.className = 'mztm-custom-select-wrapper';
        wrapper.id = `${id}_wrapper`;

        const trigger = document.createElement('div');
        trigger.className = 'mztm-custom-select-trigger';
        trigger.id = `${id}_trigger`;
        trigger.tabIndex = 0;
        const triggerText = document.createElement('span');
        triggerText.className = 'mztm-custom-select-text mztm-custom-select-placeholder';
        triggerText.textContent = placeholderText;
        trigger.appendChild(triggerText);

        const listContainer = document.createElement('div');
        listContainer.className = 'mztm-custom-select-list-container';
        listContainer.id = `${id}_list_container`;
        const list = document.createElement('ul');
        list.className = 'mztm-custom-select-list';
        list.id = `${id}_list`;
        listContainer.appendChild(list);

        addPreviewListenersToList(list);

        trigger.addEventListener('click', (e) => {
            e.stopPropagation();
            const isOpen = listContainer.classList.contains('open');
            closeAllCustomDropdowns(wrapper);
            if (!isOpen && !trigger.classList.contains('disabled')) {
                listContainer.classList.add('open');
                trigger.classList.add('open');
                currentOpenDropdown = wrapper;
            }
        });

        trigger.addEventListener('keydown', (e) => {
            if(e.key === 'Enter' || e.key === ' ') {
                e.preventDefault();
                trigger.click();
            }
        });

        list.addEventListener('click', (e) => {
            const item = e.target.closest('.mztm-custom-select-item');
            if (item && !item.classList.contains('disabled')) {
                const value = item.dataset.value || item.dataset.tacticId || item.dataset.tacticName;
                const text = item.textContent;

                triggerText.textContent = text;
                triggerText.classList.remove('mztm-custom-select-placeholder');
                trigger.dataset.selectedValue = value;

                if (id === 'tactics_selector') {
                    handleTacticSelection(value);
                } else if (id === 'complete_tactics_selector') {
                    selectedCompleteTacticName = value;
                }

                closeAllCustomDropdowns();

                const changeEvent = new Event('change', { bubbles: true });
                trigger.dispatchEvent(changeEvent);
            }
        });

        wrapper.appendChild(trigger);
        wrapper.appendChild(listContainer);
        return wrapper;
    }

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

        const dropdownWrapper = createCustomSelect('tactics_selector', USERSCRIPT_STRINGS.tacticsDropdownMenuLabel);

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

        const filterDropdownWrapper = document.createElement('div');
        filterDropdownWrapper.className = 'category-filter-wrapper';
        const filterSelect = document.createElement('select');
        filterSelect.id = 'category_filter_selector';
        filterSelect.addEventListener('change', (e) => {
            currentFilter = e.target.value;
            updateTacticsDropdown(selectedFormationTacticId);
        });

        const manageBtn = document.createElement('button');
        manageBtn.id = 'manage_items_btn';
        manageBtn.className = 'mzbtn manage-items-btn';
        manageBtn.innerHTML = '⚙️';
        manageBtn.title = USERSCRIPT_STRINGS.managementModalTitle;
        manageBtn.addEventListener('click', showManagementModal);
        filterDropdownWrapper.appendChild(filterSelect);
        filterDropdownWrapper.appendChild(manageBtn);

        appendChildren(controlsContainer, [dropdownWrapper, searchBox, filterDropdownWrapper]);
        container.appendChild(controlsContainer);
        return container;
    }

    function updateCategoryFilterDropdown() {
        const filterSelect = document.getElementById('category_filter_selector');
        if (!filterSelect) return;
        const previousValue = filterSelect.value;
        filterSelect.innerHTML = '';
        const usedCategoryIds = new Set(tactics.map(t => t.style || OTHER_CATEGORY_ID));
        let categoriesToShow = [{
            id: 'all',
            name: USERSCRIPT_STRINGS.allTacticsFilter
        }];
        Object.values(categories)
            .filter(cat => cat.id !== 'all' && (usedCategoryIds.has(cat.id) || Object.keys(DEFAULT_CATEGORIES).includes(cat.id) || cat.id === OTHER_CATEGORY_ID))
            .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);
        })
            .forEach(cat => categoriesToShow.push({ id: cat.id, name: getCategoryName(cat.id) }));

        categoriesToShow.forEach(categoryInfo => {
            const option = document.createElement('option');
            option.value = categoryInfo.id;
            option.textContent = categoryInfo.name;
            filterSelect.appendChild(option);
        });

        if (categoriesToShow.some(cat => cat.id === previousValue)) {
            filterSelect.value = previousValue;
        } else {
            filterSelect.value = 'all';
            currentFilter = 'all';
        }

        filterSelect.disabled = categoriesToShow.length <= 1;
    }

    function updateTacticsDropdown(currentSelectedId = null) {
        const listElement = document.getElementById('tactics_selector_list');
        const triggerElement = document.getElementById('tactics_selector_trigger');
        const triggerTextElement = triggerElement?.querySelector('.mztm-custom-select-text');
        const wrapper = document.getElementById('tactics_selector_wrapper');
        const searchBox = document.querySelector('.tactics-search-box');

        if (!listElement || !triggerElement || !triggerTextElement || !wrapper) return;

        listElement.innerHTML = '';

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

        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 => {
            if (id !== 'all') groupedTactics[id] = [];
        });
        if (!groupedTactics[OTHER_CATEGORY_ID]) groupedTactics[OTHER_CATEGORY_ID] = [];

        filteredTactics.forEach(t => {
            const categoryId = t.style || OTHER_CATEGORY_ID;
            if (!groupedTactics[categoryId]) {
                if (!groupedTactics[OTHER_CATEGORY_ID]) groupedTactics[OTHER_CATEGORY_ID] = [];
                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 (DEFAULT_CATEGORIES[a] && !DEFAULT_CATEGORIES[b]) return -1;
            if (!DEFAULT_CATEGORIES[a] && DEFAULT_CATEGORIES[b]) return 1;
            if (a === OTHER_CATEGORY_ID) return 1;
            if (b === OTHER_CATEGORY_ID) return -1;
            return (getCategoryName(a) || '').localeCompare(getCategoryName(b) || '');
        });

        let itemsAdded = 0;
        categoryOrder.forEach(categoryId => {
            if (groupedTactics[categoryId].length > 0) {
                addTacticItemsGroup(listElement, groupedTactics[categoryId], getCategoryName(categoryId), categoryId);
                itemsAdded += groupedTactics[categoryId].length;
            }
        });

        if (itemsAdded === 0) {
            const noResultsItem = document.createElement('li');
            noResultsItem.className = 'mztm-custom-select-no-results';
            noResultsItem.textContent = tactics.length === 0 ? USERSCRIPT_STRINGS.noTacticsSaved : USERSCRIPT_STRINGS.noTacticsFound;
            listElement.appendChild(noResultsItem);
            triggerElement.classList.add('disabled');
            triggerTextElement.textContent = tactics.length === 0 ? USERSCRIPT_STRINGS.noTacticsSaved : USERSCRIPT_STRINGS.tacticsDropdownMenuLabel;
            triggerTextElement.classList.add('mztm-custom-select-placeholder');
            delete triggerElement.dataset.selectedValue;
            selectedFormationTacticId = null;
        } else {
            triggerElement.classList.remove('disabled');
            const currentSelection = tactics.find(t => t.id === currentSelectedId);
            if (currentSelection) {
                triggerTextElement.textContent = currentSelection.name;
                triggerTextElement.classList.remove('mztm-custom-select-placeholder');
                triggerElement.dataset.selectedValue = currentSelection.id;
                selectedFormationTacticId = currentSelection.id;
            } else {
                triggerTextElement.textContent = USERSCRIPT_STRINGS.tacticsDropdownMenuLabel;
                triggerTextElement.classList.add('mztm-custom-select-placeholder');
                delete triggerElement.dataset.selectedValue;
                selectedFormationTacticId = null;
            }
        }
    }

    function addTacticItemsGroup(listElement, tacticsList, groupLabel, categoryId) {
        if (tacticsList.length === 0) return;

        const categoryHeader = document.createElement('li');
        categoryHeader.className = 'mztm-custom-select-category';
        categoryHeader.textContent = groupLabel;
        listElement.appendChild(categoryHeader);

        tacticsList.sort((a, b) => a.name.localeCompare(b.name));
        tacticsList.forEach(tactic => {
            const item = document.createElement('li');
            item.className = 'mztm-custom-select-item';
            item.textContent = tactic.name;
            item.dataset.tacticId = tactic.id;
            item.dataset.value = tactic.id;
            item.dataset.description = tactic.description || '';
            item.dataset.style = tactic.style || OTHER_CATEGORY_ID;
            item.dataset.formationString = formatFormationString(getFormation(tactic.coordinates));
            listElement.appendChild(item);
        });
    }

    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 dropdownWrapper = createCustomSelect('complete_tactics_selector', USERSCRIPT_STRINGS.completeTacticsDropdownMenuLabel);

        container.appendChild(label);
        container.appendChild(dropdownWrapper);
        return container;
    }

    function updateCompleteTacticsDropdown(currentSelectedName = null) {
        const listElement = document.getElementById('complete_tactics_selector_list');
        const triggerElement = document.getElementById('complete_tactics_selector_trigger');
        const triggerTextElement = triggerElement?.querySelector('.mztm-custom-select-text');
        const wrapper = document.getElementById('complete_tactics_selector_wrapper');

        if (!listElement || !triggerElement || !triggerTextElement || !wrapper) return;

        listElement.innerHTML = '';

        const names = Object.keys(completeTactics).sort((a, b) => a.localeCompare(b));

        if (names.length === 0) {
            const noResultsItem = document.createElement('li');
            noResultsItem.className = 'mztm-custom-select-no-results';
            noResultsItem.textContent = USERSCRIPT_STRINGS.noCompleteTacticsSaved;
            listElement.appendChild(noResultsItem);
            triggerElement.classList.add('disabled');
            triggerTextElement.textContent = USERSCRIPT_STRINGS.noCompleteTacticsSaved;
            triggerTextElement.classList.add('mztm-custom-select-placeholder');
            delete triggerElement.dataset.selectedValue;
            selectedCompleteTacticName = null;
        } else {
            triggerElement.classList.remove('disabled');
            names.forEach(name => {
                const tactic = completeTactics[name];
                const item = document.createElement('li');
                item.className = 'mztm-custom-select-item';
                item.textContent = name;
                item.dataset.tacticName = name;
                item.dataset.value = name;
                item.dataset.description = tactic.description || '';
                item.dataset.formationString = formatFormationString(getFormationFromCompleteTactic(tactic));
                listElement.appendChild(item);
            });

            const currentSelection = currentSelectedName && completeTactics[currentSelectedName] ? currentSelectedName : null;

            if (currentSelection) {
                triggerTextElement.textContent = currentSelection;
                triggerTextElement.classList.remove('mztm-custom-select-placeholder');
                triggerElement.dataset.selectedValue = currentSelection;
                selectedCompleteTacticName = currentSelection;
            } else {
                triggerTextElement.textContent = USERSCRIPT_STRINGS.completeTacticsDropdownMenuLabel;
                triggerTextElement.classList.add('mztm-custom-select-placeholder');
                delete triggerElement.dataset.selectedValue;
                selectedCompleteTacticName = null;
            }
        }
    }

    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 completeButtonsRow1 = document.createElement('div');
        completeButtonsRow1.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 renameCompleteBtn = createButton('rename_complete_tactic_button', USERSCRIPT_STRINGS.renameCompleteTacticButton, editCompleteTactic);
        const updateCompleteBtn = createButton('update_complete_tactic_button', USERSCRIPT_STRINGS.updateCompleteTacticButton, updateCompleteTactic);
        const deleteCompleteBtn = createButton('delete_complete_tactic_button', USERSCRIPT_STRINGS.deleteCompleteTacticButton, deleteCompleteTactic);
        appendChildren(completeButtonsRow1, [saveCompleteBtn, loadCompleteBtn, renameCompleteBtn, updateCompleteBtn, deleteCompleteBtn]);
        const completeButtonsRow2 = document.createElement('div');
        completeButtonsRow2.className = 'action-buttons-row';
        const importCompleteBtn = createButton('import_complete_tactics_btn', USERSCRIPT_STRINGS.importCompleteTacticsButton, importCompleteTactics);
        const exportCompleteBtn = createButton('export_complete_tactics_btn', USERSCRIPT_STRINGS.exportCompleteTacticsButton, exportCompleteTactics);
        appendChildren(completeButtonsRow2, [importCompleteBtn, exportCompleteBtn]);
        appendChildren(completeButtonsSection, [completeButtonsRow1, completeButtonsRow2]);
        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('.management-modal-content, .modal-tab-content').forEach(c => c.classList.remove('active'));
                tabButton.classList.add('active');
                const content = modalBody.querySelector(`.management-modal-content[data-tab-id="${tab.id}"], .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 formationItems = [
            { q: "<code>Add Current</code> Button (Formations Mode)", a: "Saves the player positions currently visible on the pitch as a new formation. You'll be prompted for a name, category, and an optional description." },
            { q: "<code>Add via XML</code> Button (Formations Mode)", a: "Allows pasting XML to add a new formation. Only player positions are saved from the XML. Prompted for name, category, and description." },
            { q: "Category Filter Dropdown & <code>⚙️</code> Button (Formations Mode)", a: "Use the dropdown to filter formations by category. Click the gear icon (⚙️) to open the Management Modal (Formations & Categories)." },
            { q: "<code>Edit</code> Button (Formations Mode)", a: "Allows renaming the selected formation, changing its assigned category, and editing its description via a popup." },
            { q: "<code>Update Coords</code> Button (Formations Mode)", a: "Updates the coordinates of the selected formation to match the current player positions on the pitch (description and category remain unchanged)." },
            { q: "<code>Delete</code> Button (Formations Mode)", a: "Permanently removes the selected formation from the storage." },
            { q: "<code>Import</code> Button (Formations Mode)", a: "Imports multiple formations from a JSON text format. Merges with existing formations (updates name/category/description if ID matches)." },
            { q: "<code>Export</code> Button (Formations Mode)", a: "Exports all saved formations (including descriptions) into a JSON text format (copied to clipboard)." },
            { q: "<code>Reset</code> Button (Formations Mode)", a: "Deletes all saved formations and custom categories, restoring defaults." },
            { q: "<code>Clear</code> Button (Formations Mode)", a: "Deletes all saved formations." },
            { q: "Management Modal (Gear Icon ⚙️)", a: "Opens a dedicated window to manage formations (edit name/description/category, delete) and categories (add, remove) in bulk." },
            { q: "Preview on Hover (Formations Mode)", a: "Hover your mouse over a formation name in the dropdown list to see its numerical formation (e.g., 4-4-2) and its description in a small pop-up." }
        ];

        const tacticItems = [
            { q: "<code>Save Current</code> Button (Tactics Mode)", a: "Exports the entire current tactic setup (positions, alts, rules, settings) using MZ's native export, parses it, prompts for a name and description, then saves it as a new complete tactic." },
            { q: "<code>Load</code> Button (Tactics Mode)", a: "Loads a saved complete tactic using MZ's native import. Shows a spinner during load. Matches players or substitutes if needed. Updates everything on the pitch." },
            { q: "<code>Rename</code> Button (Tactics Mode)", a: "Allows renaming the selected complete tactic and editing its description via a popup." },
            { q: "<code>Update with Current</code> Button (Tactics Mode)", a: "Overwrites the selected complete tactic's positions, rules, and settings with the setup currently on the pitch (using native export). The existing description is kept." },
            { q: "<code>Delete</code> Button (Tactics Mode)", a: "Permanently removes the selected complete tactic." },
            { q: "<code>Import</code> Button (Tactics Mode)", a: "Imports multiple complete tactics from a JSON text format. Merges with existing tactics, overwriting any with the same name (including description)." },
            { q: "<code>Export</code> Button (Tactics Mode)", a: "Exports all saved complete tactics (including descriptions) into a JSON text format (copied to clipboard)." },
            { q: "Preview on Hover (Tactics Mode)", a: "Hover your mouse over a tactic name in the dropdown list to see its numerical formation (e.g., 5-3-2, based on initial positions) and its description in a small pop-up." }
        ];

        const combinedItems = [...formationItems, ...tacticItems].sort((a,b) => {
            const modeA = a.q.includes("Formations Mode") || a.q.includes("Category Filter") || a.q.includes("Management Modal") ? 0 : (a.q.includes("Tactics Mode") ? 1 : 2);
            const modeB = b.q.includes("Formations Mode") || b.q.includes("Category Filter") || b.q.includes("Management Modal") ? 0 : (b.q.includes("Tactics Mode") ? 1 : 2);
            if (modeA !== modeB) return modeA - modeB;
            return a.q.localeCompare(b.q);
        });

        combinedItems.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;
                if (!t.hasOwnProperty('description')) t.description = '';
            });
            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 formations 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));
            let dataChanged = false;
            tacticData.tactics.forEach(t => {
                if (!t.hasOwnProperty('style')) {
                    t.style = OTHER_CATEGORY_ID;
                    dataChanged = true;
                }
                if (!t.hasOwnProperty('description')) {
                    t.description = '';
                    dataChanged = true;
                }
            });
            if(dataChanged) GM_setValue(FORMATIONS_STORAGE_KEY, tacticData);
        }
        tactics = tacticData.tactics || [];
        tactics.sort((a, b) => a.name.localeCompare(b.name));
        loadCompleteTacticsData();
        const storedCompleteTactics = GM_getValue(COMPLETE_TACTICS_STORAGE_KEY, {});
        let completeTacticsChanged = false;
        for (const name in storedCompleteTactics) {
            if (storedCompleteTactics.hasOwnProperty(name)) {
                if (!storedCompleteTactics[name].hasOwnProperty('description')) {
                    storedCompleteTactics[name].description = '';
                    completeTacticsChanged = true;
                }
            }
        }
        if (completeTacticsChanged) GM_setValue(COMPLETE_TACTICS_STORAGE_KEY, storedCompleteTactics);
        completeTactics = storedCompleteTactics;
    }

    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();
            createTacticPreviewElement();
            await initializeScriptData();
            const mainContainer = createMainContainer();
            setUpTacticsInterface(mainContainer);
            insertAfterElement(mainContainer, tacticsBox);
            updateTacticsDropdown();
            updateCategoryFilterDropdown();
            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);
})();