MZ - Tactics on Hover (League Pages)

Displays most recent tactics in league pages (on match hover or via dropdown in the schedule tab)

// ==UserScript==
// @name         MZ - Tactics on Hover (League Pages)
// @namespace    douglaskampl
// @version      2.0
// @description  Displays most recent tactics in league pages (on match hover or via dropdown in the schedule tab)
// @author       Douglas
// @match        https://www.managerzone.com/?p=league&type*
// @grant        none
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    let teamsDataStore = [];
    let allMatchesDataStore = [];
    let isScheduleFeatureInitialized = false;
    let tacticsModalElement = null;
    let floatingDropdownElement = null;

    function injectCustomFont() {
        if (document.getElementById('mz-font-import-dm-sans')) {
            return;
        }
        const fontLink = document.createElement('link');
        fontLink.id = 'mz-font-import-dm-sans';
        fontLink.rel = 'preconnect';
        fontLink.href = 'https://fonts.googleapis.com';
        document.head.appendChild(fontLink);

        const fontLinkCss = document.createElement('link');
        fontLinkCss.rel = 'stylesheet';
        fontLinkCss.href = 'https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap';
        document.head.appendChild(fontLinkCss);
    }

    function injectCustomStyles() {
        if (document.getElementById('mz-tactics-custom-styles-v2')) {
            return;
        }
        const style = document.createElement('style');
        style.id = 'mz-tactics-custom-styles-v2';
        style.textContent = `
            @keyframes mz-spinner-spin {
                to { transform: rotate(360deg); }
            }
            .mz-spinner {
                display: inline-block;
                width: 16px;
                height: 16px;
                margin-left: 10px;
                border: 2px solid rgba(255, 255, 255, 0.3);
                border-radius: 50%;
                border-top-color: #fff;
                animation: mz-spinner-spin 1s ease-in-out infinite;
                vertical-align: middle;
            }
        `;
        document.head.appendChild(style);
    }

    function getTeamNameFromCell(cell) {
        if (!cell) {
            return '';
        }
        return cell.firstChild?.textContent.trim() ?? '';
    }

    function extractUniqueTeamsFromSchedule(schedulePanelElement) {
        const teamNames = new Set();
        if (!schedulePanelElement) {
            return [];
        }

        const roundTables = schedulePanelElement.querySelectorAll('table.hitlist.marker');
        roundTables.forEach(table => {
            if (!table.tBodies[0]) {
                return;
            }
            const rows = table.tBodies[0].querySelectorAll('tr');
            rows.forEach(row => {
                if (row.cells && row.cells.length >= 3) {
                    const homeTeamName = getTeamNameFromCell(row.cells[0]);
                    const awayTeamName = getTeamNameFromCell(row.cells[2]);
                    if (homeTeamName) {
                        teamNames.add(homeTeamName);
                    }
                    if (awayTeamName) {
                        teamNames.add(awayTeamName);
                    }
                }
            });
        });
        return Array.from(teamNames).map(name => ({ name })).sort((a, b) => a.name.localeCompare(b.name));
    }

    function extractAllMatchesFromSchedule(schedulePanelElement) {
        const matches = [];
        if (!schedulePanelElement) {
            return matches;
        }

        const contentElements = schedulePanelElement.querySelectorAll('h2.subheader, div.mainContent');
        let currentDate = 'Date N/A';

        contentElements.forEach(element => {
            if (element.tagName === 'H2' && element.classList.contains('subheader')) {
                const headerText = element.textContent.trim();
                const dateMatch = headerText.match(/(\d{1,2}\/\d{1,2}\/\d{4})/);
                if (dateMatch && dateMatch[1]) {
                    currentDate = dateMatch[1];
                }
            } else if (element.tagName === 'DIV' && element.classList.contains('mainContent')) {
                const table = element.querySelector('table.hitlist.marker');
                if (table && table.tBodies[0]) {
                    const rows = table.tBodies[0].querySelectorAll('tr');
                    rows.forEach(row => {
                        if (row.cells && row.cells.length >= 3) {
                            const matchLinkCell = row.cells[1];
                            const matchLink = matchLinkCell ? matchLinkCell.querySelector('a[href*="mid="]') : null;

                            if (matchLink && matchLink.textContent.trim().toLowerCase() !== 'x - x') {
                                const homeTeamName = getTeamNameFromCell(row.cells[0]);
                                const awayTeamName = getTeamNameFromCell(row.cells[2]);
                                const href = matchLink.getAttribute('href');
                                const midMatch = href.match(/mid=(\d+)/);
                                if (homeTeamName && awayTeamName && midMatch && midMatch[1]) {
                                    matches.push({
                                        mid: midMatch[1],
                                        homeTeam: homeTeamName,
                                        awayTeam: awayTeamName,
                                        date: currentDate
                                    });
                                }
                            }
                        }
                    });
                }
            }
        });
        return matches;
    }

    function parseDate(dateString) {
        const parts = dateString.split('/');
        if (parts.length === 3) {
            return new Date(parts[2], parts[1] - 1, parts[0]);
        }
        return new Date(0);
    }

    function createTacticsModalStructure() {
        if (document.getElementById('mz-team-tactics-modal-v2')) {
            tacticsModalElement = document.getElementById('mz-team-tactics-modal-v2');
            return;
        }

        const modal = document.createElement('div');
        modal.id = 'mz-team-tactics-modal-v2';
        Object.assign(modal.style, {
            position: 'fixed',
            top: '0',
            left: '0',
            width: '100%',
            height: '100%',
            backgroundColor: 'rgba(10, 10, 15, 0.85)',
            zIndex: '30000',
            display: 'none',
            alignItems: 'center',
            justifyContent: 'center',
            fontFamily: "'DM Sans', sans-serif",
            backdropFilter: 'blur(3px)',
        });
        modal.onclick = (event) => {
            if (event.target === modal) {
                modal.style.display = 'none';
            }
        };

        const modalContent = document.createElement('div');
        Object.assign(modalContent.style, {
            backgroundColor: '#2c2f36',
            color: '#d1d1d1',
            padding: '25px',
            border: '1px solid #40444c',
            borderRadius: '8px',
            maxHeight: '90vh',
            maxWidth: '900px',
            width: '90%',
            overflowY: 'auto',
            boxShadow: '0 5px 25px rgba(0,0,0,0.5)',
            position: 'relative',
        });

        const modalTitle = document.createElement('h3');
        modalTitle.id = 'mz-team-tactics-modal-v2-title';
        Object.assign(modalTitle.style, {
            fontSize: '1.4em',
            fontWeight: '700',
            marginBottom: '20px',
            color: '#ffffff',
            borderBottom: '1px solid #40444c',
            paddingBottom: '15px',
            textAlign: 'center',
        });

        const closeButton = document.createElement('button');
        closeButton.textContent = '×';
        Object.assign(closeButton.style, {
            position: 'absolute',
            top: '10px',
            right: '15px',
            background: 'transparent',
            color: '#a0a0a0',
            border: 'none',
            fontSize: '28px',
            lineHeight: '1',
            cursor: 'pointer',
            padding: '5px 10px',
            borderRadius: '4px',
            transition: 'color 0.2s',
        });
        closeButton.onmouseover = () => closeButton.style.color = '#ffffff';
        closeButton.onmouseout = () => closeButton.style.color = '#a0a0a0';
        closeButton.onclick = () => { modal.style.display = 'none'; };

        const imagesContainer = document.createElement('div');
        imagesContainer.id = 'mz-team-tactics-modal-v2-images';

        modalContent.append(closeButton, modalTitle, imagesContainer);
        modal.appendChild(modalContent);
        document.body.appendChild(modal);
        tacticsModalElement = modal;
    }

    function displayTeamTacticsInModal(selectedTeamName) {
        if (!tacticsModalElement) {
            createTacticsModalStructure();
        }

        const modalTitle = tacticsModalElement.querySelector('#mz-team-tactics-modal-v2-title');
        const imagesContainer = tacticsModalElement.querySelector('#mz-team-tactics-modal-v2-images');

        modalTitle.textContent = `${selectedTeamName} Recent Tactics`;
        imagesContainer.innerHTML = '';

        const teamMatches = allMatchesDataStore.filter(match =>
            (match.homeTeam === selectedTeamName || match.awayTeam === selectedTeamName)
        );

        if (teamMatches.length === 0) {
            imagesContainer.textContent = 'No played matches found or tactics are unavailable.';
            imagesContainer.style.textAlign = 'center';
            imagesContainer.style.display = 'block';
        } else {
            Object.assign(imagesContainer.style, {
                display: 'flex',
                flexWrap: 'wrap',
                gap: '20px',
                justifyContent: 'flex-start',
            });

            teamMatches.sort((a, b) => parseDate(b.date) - parseDate(a.date));

            teamMatches.forEach(match => {
                const entryDiv = document.createElement('div');
                Object.assign(entryDiv.style, {
                    width: 'calc(50% - 10px)',
                    boxSizing: 'border-box',
                    padding: '15px',
                    borderRadius: '6px',
                    border: '1px solid #40444c',
                    backgroundColor: 'rgba(0, 0, 0, 0.1)',
                });

                const dateElement = document.createElement('p');
                dateElement.textContent = `Date: ${match.date}`;
                Object.assign(dateElement.style, {
                    fontSize: '1em',
                    color: '#e0e0e0',
                    marginBottom: '10px',
                    fontWeight: '500',
                    textAlign: 'center',
                });

                const imgUrl = `https://www.managerzone.com/dynimg/pitch.php?match_id=${match.mid}`;
                const imgElement = document.createElement('img');
                imgElement.src = imgUrl;
                imgElement.alt = `Tactic from match ${match.mid} on ${match.date}`;
                Object.assign(imgElement.style, {
                    maxWidth: '100%',
                    height: 'auto',
                    display: 'block',
                    border: '1px solid #4a4e56',
                    borderRadius: '4px',
                    margin: '0 auto 8px auto',
                });

                const detailsRow = document.createElement('div');
                 Object.assign(detailsRow.style, {
                    display: 'flex',
                    justifyContent: 'space-between',
                    alignItems: 'center',
                    marginTop: '10px',
                    gap: '10px',
                });

                const location = match.homeTeam === selectedTeamName ? 'Home' : 'Away';
                const explanationContainer = document.createElement('div');
                 Object.assign(explanationContainer.style, {
                    fontSize: '0.9em',
                    color: '#c0c5ce',
                    display: 'flex',
                    alignItems: 'center',
                });

                const marker = document.createElement('span');
                Object.assign(marker.style, {
                    display: 'inline-block',
                    width: '10px',
                    height: '10px',
                    borderRadius: '3px',
                    marginRight: '8px',
                    verticalAlign: 'middle',
                    backgroundColor: (location === 'Home') ? '#ffd600' : '#111111',
                    border: '1px solid rgba(255, 255, 255, 0.2)'
                });

                explanationContainer.appendChild(marker);
                explanationContainer.appendChild(document.createTextNode(location === 'Home' ? 'Yellow.' : 'Black.'));

                const opponent = match.homeTeam === selectedTeamName ? match.awayTeam : match.homeTeam;
                const caption = document.createElement('p');
                caption.textContent = `vs: ${opponent} (${location}) | ID: ${match.mid}`;
                Object.assign(caption.style, {
                    fontSize: '0.85em',
                    textAlign: 'right',
                    color: '#a0a5ad',
                    margin: '0',
                    flexShrink: '0',
                });

                detailsRow.append(explanationContainer, caption);
                entryDiv.append(dateElement, imgElement, detailsRow);
                imagesContainer.appendChild(entryDiv);
            });
        }
        tacticsModalElement.style.display = 'flex';
        if (imagesContainer) {
            imagesContainer.scrollTop = 0;
        }
    }

    function createFloatingDropdownMenu() {
        if (document.getElementById('mz-floating-tactics-container-v2')) {
            floatingDropdownElement = document.getElementById('mz-floating-tactics-container-v2');
            const select = floatingDropdownElement.querySelector('select');
            if (select) {
                const currentValue = select.value;
                while (select.options.length > 1) {
                    select.remove(1);
                }
                teamsDataStore.forEach(team => {
                    const option = document.createElement('option');
                    option.value = team.name;
                    option.textContent = team.name;
                    select.appendChild(option);
                });
                select.value = currentValue;
            }
            return;
        }

        const container = document.createElement('div');
        container.id = 'mz-floating-tactics-container-v2';
        Object.assign(container.style, {
            position: 'fixed',
            top: '69px',
            right: '15px',
            zIndex: '20000',
            background: 'linear-gradient(160deg, #000000 0%, #0f0c29 35%, #7209b7 70%, #00f5d4 100%)',
            padding: '10px 15px',
            borderRadius: '5px',
            boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
            border: '1px solid #40444c',
            display: 'none',
            fontFamily: "'DM Sans', sans-serif",
            alignItems: 'center',
        });

        const label = document.createElement('label');
        label.setAttribute('for', 'mz-floating-tactics-select-v2');
        label.textContent = 'Team: ';
        Object.assign(label.style, {
            color: '#efefef',
            marginRight: '8px',
            fontSize: '12px',
            verticalAlign: 'middle',
            fontWeight: '500',
        });

        const select = document.createElement('select');
        select.id = 'mz-floating-tactics-select-v2';
        select.disabled = true;
        Object.assign(select.style, {
            padding: '4px 6px',
            fontSize: '12px',
            backgroundColor: '#ffffff',
            border: '1px solid #cccccc',
            borderRadius: '3px',
            verticalAlign: 'middle',
        });

        const defaultOption = document.createElement('option');
        defaultOption.value = '';
        defaultOption.textContent = 'Select a Team';
        defaultOption.disabled = true;
        defaultOption.selected = true;
        select.appendChild(defaultOption);

        teamsDataStore.forEach(team => {
            const option = document.createElement('option');
            option.value = team.name;
            option.textContent = team.name;
            select.appendChild(option);
        });

        select.onchange = function () {
            if (this.value) {
                displayTeamTacticsInModal(this.value);
                this.selectedIndex = 0;
            }
        };

        const spinner = document.createElement('div');
        spinner.className = 'mz-spinner';

        container.append(label, select, spinner);
        document.body.appendChild(container);
        floatingDropdownElement = container;
    }

    function showFloatingDropdown() {
        if (floatingDropdownElement) {
            floatingDropdownElement.style.display = 'flex';
        }
    }

    function hideFloatingDropdown() {
        if (floatingDropdownElement) {
            floatingDropdownElement.style.display = 'none';
        }
    }

    function initializeScheduleFeature(schedulePanel) {
        teamsDataStore = extractUniqueTeamsFromSchedule(schedulePanel);
        allMatchesDataStore = extractAllMatchesFromSchedule(schedulePanel);

        if (teamsDataStore.length === 0) {
            return false;
        }
        injectCustomFont();
        injectCustomStyles();
        createTacticsModalStructure();
        createFloatingDropdownMenu();
        isScheduleFeatureInitialized = true;
        return true;
    }

    function handleActiveTabChange(activeTabLinkElement) {
        const panelId = activeTabLinkElement.getAttribute('aria-controls');
        const isScheduleTabActive = panelId === 'ui-tabs-3' || (activeTabLinkElement.href && activeTabLinkElement.href.includes('&sub=schedule'));

        if (isScheduleTabActive) {
            const schedulePanel = document.getElementById(panelId || 'ui-tabs-3');
            if (schedulePanel && schedulePanel.offsetParent !== null) {
                let attempts = 0;
                const maxAttempts = 10;
                const contentWaitInterval = setInterval(() => {
                    attempts++;
                    const hasContent = schedulePanel.querySelector('table.hitlist.marker') && schedulePanel.querySelector('table.hitlist.marker tr td a[href*="mid="]');
                    if (hasContent) {
                        clearInterval(contentWaitInterval);
                        if (!isScheduleFeatureInitialized) {
                            const success = initializeScheduleFeature(schedulePanel);
                            if (!success) {
                                hideFloatingDropdown();
                                return;
                            }
                        }
                        showFloatingDropdown();
                        if (floatingDropdownElement) {
                            floatingDropdownElement.querySelector('select').disabled = false;
                            const spinner = floatingDropdownElement.querySelector('.mz-spinner');
                            if (spinner) {
                                spinner.style.display = 'none';
                            }
                        }
                    } else if (attempts >= maxAttempts) {
                        clearInterval(contentWaitInterval);
                        hideFloatingDropdown();
                    }
                }, 500);
            } else {
                hideFloatingDropdown();
            }
        } else {
            hideFloatingDropdown();
        }
    }

    function setUpTabChangeObserver() {
        const tabsNav = document.querySelector('ul.ui-tabs-nav');
        if (!tabsNav) {
            return;
        }

        const tabChangeObserver = new MutationObserver(() => {
            const activeLi = tabsNav.querySelector('li.ui-tabs-active');
            if (activeLi) {
                const activeTabLink = activeLi.querySelector('a.ui-tabs-anchor');
                if (activeTabLink) {
                    handleActiveTabChange(activeTabLink);
                } else {
                    hideFloatingDropdown();
                }
            } else {
                hideFloatingDropdown();
            }
        });

        tabsNav.querySelectorAll('li.ui-state-default').forEach(li => {
            tabChangeObserver.observe(li, { attributes: true, attributeFilter: ['class', 'aria-selected'] });
        });

        const initiallyActiveLi = tabsNav.querySelector('li.ui-tabs-active');
        if (initiallyActiveLi) {
            const activeTabLink = initiallyActiveLi.querySelector('a.ui-tabs-anchor');
            if (activeTabLink) {
                handleActiveTabChange(activeTabLink);
            }
        } else {
             hideFloatingDropdown();
        }
    }

    function addHoverTooltipsToMatchLinks(panel) {
        if (!panel) {
            return;
        }
        const anchors = panel.querySelectorAll('a[href*="mid="]:not([data-mz-tactics-processed])');
        anchors.forEach(anchor => {
            const text = anchor.textContent.trim();
            if (text.toLowerCase() === 'x - x') {
                return;
            }

            const href = anchor.getAttribute('href');
            const midMatch = href.match(/mid=(\d+)/);
            if (!midMatch) {
                return;
            }

            const mid = midMatch[1];
            const imgUrl = `https://www.managerzone.com/dynimg/pitch.php?match_id=${mid}`;
            const tooltip = document.createElement('img');
            tooltip.src = imgUrl;
            Object.assign(tooltip.style, {
                position: 'absolute',
                border: '1px solid #ccc',
                background: '#fff',
                display: 'none',
                zIndex: '1000',
                maxWidth: '400px',
                boxShadow: '0 0 10px rgba(0,0,0,0.5)',
            });
            document.body.appendChild(tooltip);

            anchor.addEventListener('mouseenter', (e) => {
                const rect = e.target.getBoundingClientRect();
                tooltip.style.top = `${rect.bottom + window.scrollY + 5}px`;
                tooltip.style.left = `${rect.left + window.scrollX}px`;
                tooltip.style.display = 'block';
            });
            anchor.addEventListener('mouseleave', () => {
                tooltip.style.display = 'none';
            });
            anchor.setAttribute('data-mz-tactics-processed', 'true');
        });
    }

    function observePanelForDOMChanges(panel) {
        if (!panel) {
            return;
        }
        const observerConfig = { childList: true, subtree: true };
        const observer = new MutationObserver((mutations) => {
            let addedNodes = false;
            for (const mutation of mutations) {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    addedNodes = true;
                    break;
                }
            }
            if (addedNodes) {
                addHoverTooltipsToMatchLinks(panel);
            }
        });
        observer.observe(panel, observerConfig);
    }

    function initializeHoverFeature() {
        const panels = document.querySelectorAll('.ui-tabs-panel');
        panels.forEach(panel => {
            addHoverTooltipsToMatchLinks(panel);
            observePanelForDOMChanges(panel);
        });
    }

    function initializeUs() {
        let attempts = 0;
        const maxAttempts = 30;
        const interval = setInterval(() => {
            attempts++;
            const panels = document.querySelectorAll('.ui-tabs-panel');
            const nav = document.querySelector('ul.ui-tabs-nav');
            if (panels.length > 0 && nav) {
                clearInterval(interval);
                initializeHoverFeature();
                setUpTabChangeObserver();
            } else if (attempts >= maxAttempts) {
                clearInterval(interval);
            }
        }, 500);
    }

    initializeUs();
})();