ylOppTacticsPreview (Modified)

Shows the latest tactics used by an opponent

// ==UserScript==
// @name         ylOppTacticsPreview (Modified)
// @namespace    douglaskampl
// @version      5.0.1
// @description  Shows the latest tactics used by an opponent
// @author       kostrzak16 (feat. Douglas and xente)
// @match        https://www.managerzone.com/?p=match&sub=scheduled
// @icon         https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @grant        GM_getValue
// @grant        GM_setValue
// @require      https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.min.js
// @resource     oppTacticsPreviewStyles https://br18.org/mz/userscript/other/Slezsko.css
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    class OpponentTacticsPreview {
        static CONSTANTS = {
            MATCH_TYPE_GROUPS: {
                'All': [
                    { id: 'no_restriction', label: 'Senior' },
                    { id: 'u23', label: 'U23' },
                    { id: 'u21', label: 'U21' },
                    { id: 'u18', label: 'U18' }
                ],
                'World League': [
                    { id: 'world_series', label: 'Senior WL' },
                    { id: 'u23_world_series', label: 'U23 WL' },
                    { id: 'u21_world_series', label: 'U21 WL' },
                    { id: 'u18_world_series', label: 'U18 WL' }
                ],
                'Official League': [
                    { id: 'series', label: 'Senior League' },
                    { id: 'u23_series', label: 'U23 League' },
                    { id: 'u21_series', label: 'U21 League' },
                    { id: 'u18_series', label: 'U18 League' }
                ]
            },
            URLS: {
                MATCH_STATS: (matchId) => `https://www.managerzone.com/matchviewer/getMatchFiles.php?type=stats&mid=${matchId}&sport=soccer`,
                MATCH_LIST: 'https://www.managerzone.com/ajax.php?p=matches&sub=list&sport=soccer',
                PITCH_IMAGE: (matchId) => `https://www.managerzone.com/dynimg/pitch.php?match_id=${matchId}`,
                MATCH_RESULT: (matchId) => `https://www.managerzone.com/?p=match&sub=result&mid=${matchId}`,
                CLUBHOUSE: 'https://www.managerzone.com/?p=clubhouse'
            },
            STORAGE_KEYS: {
                MATCH_LIMIT: 'ylopp_match_limit',
                SAVED_TEAMS: 'ylopp_saved_teams',
                USER_TEAM_ID: 'ylopp_user_team_id'
            },
            DEFAULTS: {
                MATCH_LIMIT: 10,
                MAX_SAVED_TEAMS: 15,
                MAX_MATCH_LIMIT: 100
            },
            SELECTORS: {
                FIXTURES_LIST: '#fixtures-results-list-wrapper',
                STATS_XENTE: '#legendDiv',
                ELO_SCHEDULED: '#eloScheduledSelect',
                HOME_TEAM: '.home-team-column.flex-grow-1',
                SELECT_WRAPPER: 'dd.set-default-wrapper'
            },
        };

        constructor() {
            this.ourTeamName = null;
            this.userTeamId = null;
            this.currentOpponentTid = '';
            this.spinnerInstance = null;
            this.observer = new MutationObserver(() => {
                this.insertIconsAndListeners();
            });
        }

        getMatchLimit() {
            return GM_getValue(OpponentTacticsPreview.CONSTANTS.STORAGE_KEYS.MATCH_LIMIT, OpponentTacticsPreview.CONSTANTS.DEFAULTS.MATCH_LIMIT);
        }

        setMatchLimit(limit) {
            const numericLimit = parseInt(limit, 10);
            if (!isNaN(numericLimit) && numericLimit > 0 && numericLimit <= OpponentTacticsPreview.CONSTANTS.DEFAULTS.MAX_MATCH_LIMIT) {
                GM_setValue(OpponentTacticsPreview.CONSTANTS.STORAGE_KEYS.MATCH_LIMIT, numericLimit);
            }
        }

        getSavedTeams() {
            return GM_getValue(OpponentTacticsPreview.CONSTANTS.STORAGE_KEYS.SAVED_TEAMS, []);
        }

        saveTeam(teamId, teamName) {
            if (!teamId || !teamName || teamName.startsWith('Team ')) {
                return;
            }
            let teams = this.getSavedTeams();
            const existingIndex = teams.findIndex(team => team.id === teamId);
            if (existingIndex > -1) {
                teams.splice(existingIndex, 1);
            }
            teams.unshift({ id: teamId, name: teamName });
            const trimmedTeams = teams.slice(0, OpponentTacticsPreview.CONSTANTS.DEFAULTS.MAX_SAVED_TEAMS);
            GM_setValue(OpponentTacticsPreview.CONSTANTS.STORAGE_KEYS.SAVED_TEAMS, trimmedTeams);
        }

        startObserving() {
            const fixturesList = document.querySelector(OpponentTacticsPreview.CONSTANTS.SELECTORS.FIXTURES_LIST);
            if (fixturesList) {
                this.observer.observe(fixturesList, { childList: true, subtree: true });
            }
        }

        showLoadingSpinner() {
            if (this.spinnerInstance) {
                return;
            }
            const spinnerContainer = document.createElement('div');
            spinnerContainer.id = 'spinjs-overlay';
            document.body.appendChild(spinnerContainer);
            this.spinnerInstance = new Spinner({ color: '#FFFFFF', lines: 12, top: '50%', left: '50%' }).spin(spinnerContainer);
        }

        hideLoadingSpinner() {
            if (this.spinnerInstance) {
                this.spinnerInstance.stop();
                this.spinnerInstance = null;
            }
            const spinnerContainer = document.getElementById('spinjs-overlay');
            if (spinnerContainer) {
                spinnerContainer.remove();
            }
        }

        extractTeamNameFromHtml(htmlDocument, teamId) {
            const nameCounts = new Map();
            const teamLinks = htmlDocument.querySelectorAll('.teams-wrapper a.clippable');
            teamLinks.forEach(link => {
                const linkUrl = new URL(link.href, location.href);
                const linkTid = linkUrl.searchParams.get('tid');
                if (linkTid === teamId) {
                    const fullName = link.querySelector('.full-name')?.textContent.trim();
                    if (fullName) {
                        nameCounts.set(fullName, (nameCounts.get(fullName) || 0) + 1);
                    }
                }
            });

            if (nameCounts.size > 0) {
                let mostCommonName = '';
                let maxCount = 0;
                for (const [name, count] of nameCounts.entries()) {
                    if (count > maxCount) {
                        maxCount = count;
                        mostCommonName = name;
                    }
                }
                return mostCommonName;
            }

            const boldTeamNameElement = htmlDocument.querySelector('.teams-wrapper a.clippable > strong > .full-name');
            if (boldTeamNameElement) {
                return boldTeamNameElement.textContent.trim();
            }

            return null;
        }

        async fetchLatestTactics(teamId, matchType) {
            const modal = document.getElementById('interaction-modal');
            if (modal) {
                this.fadeOutAndRemove(modal);
            }
            this.showLoadingSpinner();
            try {
                const response = await fetch(
                    OpponentTacticsPreview.CONSTANTS.URLS.MATCH_LIST, {
                        method: 'POST',
                        headers: { 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },
                        body: `type=played&hidescore=false&tid1=${teamId}&offset=&selectType=${matchType}&limit=max`,
                        credentials: 'include'
                    }
                );
                if (!response.ok) {
                    throw new Error(`Network response was not ok: ${response.statusText}`);
                }

                const data = await response.json();
                const parser = new DOMParser();
                const htmlDocument = parser.parseFromString(data.list, 'text/html');
                const actualTeamName = this.extractTeamNameFromHtml(htmlDocument, teamId);
                const finalTeamName = actualTeamName || `Team ${teamId}`;

                this.saveTeam(teamId, finalTeamName);
                this.currentOpponentTid = teamId;
                this.processTacticsData(htmlDocument, matchType, finalTeamName);
            } catch (error) {
                console.error('Failed to fetch latest tactics:', error);
            } finally {
                this.hideLoadingSpinner();
            }
        }

        isRelevantMatch(entry) {
            const wrapper = entry.querySelector('.responsive-hide.match-reference-text-wrapper');
            if (!wrapper) {
                return true;
            }
            const hasLink = wrapper.querySelector('a');
            return hasLink !== null;
        }

        processTacticsData(htmlDocument, matchType, opponentName) {
            const matchEntries = htmlDocument.querySelectorAll('dl > dd.odd');
            const container = this.createTacticsContainer(matchType, opponentName);
            document.body.appendChild(container);
            const listWrapper = container.querySelector('.tactics-list');
            let processedCount = 0;
            const matchLimit = this.getMatchLimit();

            for (const entry of matchEntries) {
                if (processedCount >= matchLimit) {
                    break;
                }

                if (!this.isRelevantMatch(entry)) {
                    continue;
                }

                const link = entry.querySelector('a.score-shown');
                if (!link) {
                    continue;
                }

                const dl = link.closest('dl');
                const theScore = link.textContent.trim();
                const homeTeamName = dl.querySelector('.home-team-column .full-name')?.textContent.trim() || 'Home';
                const awayTeamName = dl.querySelector('.away-team-column .full-name')?.textContent.trim() || 'Away';
                const mid = new URLSearchParams(new URL(link.href, location.href).search).get('mid');
                if (!mid) {
                    continue;
                }

                let [homeGoals, awayGoals] = [0, 0];
                if (theScore.includes('-')) {
                    const parts = theScore.split('-').map(x => parseInt(x.trim(), 10));
                    if (parts.length === 2 && !isNaN(parts[0]) && !isNaN(parts[1])) {
                        [homeGoals, awayGoals] = parts;
                    }
                }

                const opponentIsHome = (homeTeamName === opponentName);
                const tacticUrl = OpponentTacticsPreview.CONSTANTS.URLS.PITCH_IMAGE(mid);
                const resultUrl = OpponentTacticsPreview.CONSTANTS.URLS.MATCH_RESULT(mid);
                const canvas = this.createCanvasWithReplacedColors(tacticUrl, opponentIsHome);
                const item = document.createElement('div');
                item.className = 'tactic-item';

                const opponentGoals = opponentIsHome ? homeGoals : awayGoals;
                const otherGoals = opponentIsHome ? awayGoals : homeGoals;

                if (opponentGoals > otherGoals) {
                    item.classList.add('tactic-win');
                } else if (opponentGoals < otherGoals) {
                    item.classList.add('tactic-loss');
                } else {
                    item.classList.add('tactic-draw');
                }

                const linkA = document.createElement('a');
                linkA.href = resultUrl;
                linkA.target = '_blank';
                linkA.className = 'tactic-link';
                linkA.appendChild(canvas);

                const scoreP = document.createElement('p');
                scoreP.textContent = `${homeTeamName} ${theScore} ${awayTeamName}`;
                linkA.appendChild(scoreP);
                item.appendChild(linkA);

                this.addPlaystyleHover(mid, canvas, this.currentOpponentTid);
                listWrapper.appendChild(item);
                processedCount++;
            }

            if (processedCount === 0) {
                const message = document.createElement('div');
                message.className = 'no-tactics-message';
                message.textContent = 'No recent valid tactics found for this team and category.';
                listWrapper.appendChild(message);
            }
            container.classList.add('fade-in');
        }

        showInteractionModal(teamId, sourceElement) {
            const existingModal = document.getElementById('interaction-modal');
            if (existingModal) {
                this.fadeOutAndRemove(existingModal);
            }
            const modal = document.createElement('div');
            modal.id = 'interaction-modal';
            modal.classList.add('fade-in');
            const header = document.createElement('div');
            header.className = 'interaction-modal-header';
            const title = document.createElement('span');
            title.textContent = '';
            header.appendChild(title);
            const settingsIcon = document.createElement('span');
            settingsIcon.className = 'settings-icon';
            settingsIcon.innerHTML = '⚙';
            header.appendChild(settingsIcon);
            modal.appendChild(header);
            const teamInputSection = this.createTeamInputSection(modal, teamId);
            this.createTabbedButtons(modal, teamInputSection.teamIdInput);
            const settingsPanel = this.createSettingsPanel(modal);
            settingsIcon.onclick = () => {
                settingsPanel.style.display = settingsPanel.style.display === 'block' ? 'none' : 'block';
            };
            document.body.appendChild(modal);
            const rect = sourceElement.getBoundingClientRect();
            modal.style.position = 'absolute';
            modal.style.top = `${window.scrollY + rect.bottom + 5}px`;
            modal.style.left = `${window.scrollX + rect.left}px`;
        }

        createTeamInputSection(container, initialTeamId) {
            const section = document.createElement('div');
            section.className = 'interaction-section team-input-section';
            const label = document.createElement('label');
            label.textContent = 'Team ID:';
            label.htmlFor = 'team-id-input';
            section.appendChild(label);
            const teamIdInput = document.createElement('input');
            teamIdInput.type = 'text';
            teamIdInput.id = 'team-id-input';
            teamIdInput.value = initialTeamId;
            section.appendChild(teamIdInput);
            const select = this.createRecentsDropdown(teamIdInput);
            section.appendChild(select);
            container.appendChild(section);
            return { teamIdInput, recentsSelect: select };
        }

        createRecentsDropdown(teamIdInput) {
            const select = document.createElement('select');
            select.className = 'recents-select';
            const defaultOption = document.createElement('option');
            defaultOption.textContent = 'Recent Teams';
            defaultOption.value = '';
            select.appendChild(defaultOption);
            this.getSavedTeams().forEach(team => {
                const option = document.createElement('option');
                option.value = team.id;
                option.textContent = `${team.name} (${team.id})`;
                select.appendChild(option);
            });
            select.onchange = () => {
                if (select.value) {
                    teamIdInput.value = select.value;
                }
            };
            return select;
        }

        createTabbedButtons(container, teamIdInput) {
            const tabContainer = document.createElement('div');
            tabContainer.className = 'tab-container';
            const tabHeaders = document.createElement('div');
            tabHeaders.className = 'tab-headers';
            const tabContents = document.createElement('div');
            tabContents.className = 'tab-contents';
            Object.entries(OpponentTacticsPreview.CONSTANTS.MATCH_TYPE_GROUPS).forEach(([groupName, types], index) => {
                const header = document.createElement('button');
                header.className = 'tab-header';
                header.textContent = groupName;
                const content = document.createElement('div');
                content.className = 'tab-content';
                types.forEach(type => {
                    const button = document.createElement('button');
                    button.textContent = type.label;
                    button.onclick = () => {
                        const teamId = teamIdInput.value.trim();
                        if (teamId) {
                            this.fetchLatestTactics(teamId, type.id);
                        }
                    };
                    content.appendChild(button);
                });
                header.onclick = () => {
                    tabContainer.querySelectorAll('.tab-header').forEach(h => h.classList.remove('active'));
                    tabContainer.querySelectorAll('.tab-content').forEach(c => c.style.display = 'none');
                    header.classList.add('active');
                    content.style.display = 'flex';
                };
                tabHeaders.appendChild(header);
                tabContents.appendChild(content);
                if (index === 0) {
                    header.classList.add('active');
                    content.style.display = 'flex';
                } else {
                    content.style.display = 'none';
                }
            });
            tabContainer.appendChild(tabHeaders);
            tabContainer.appendChild(tabContents);
            container.appendChild(tabContainer);
        }

        createSettingsPanel(modalContainer) {
            const panel = document.createElement('div');
            panel.className = 'settings-panel';
            panel.style.display = 'none';
            const limitLabel = document.createElement('label');
            limitLabel.textContent = `Match Limit (1-${OpponentTacticsPreview.CONSTANTS.DEFAULTS.MAX_MATCH_LIMIT}):`;
            panel.appendChild(limitLabel);
            const limitInput = document.createElement('input');
            limitInput.type = 'text';
            limitInput.inputMode = 'numeric';
            limitInput.pattern = '[0-9]*';
            limitInput.value = this.getMatchLimit();
            limitInput.oninput = () => {
                limitInput.value = limitInput.value.replace(/\D/g, '');
            };
            limitInput.onchange = () => this.setMatchLimit(limitInput.value);
            panel.appendChild(limitInput);
            modalContainer.appendChild(panel);
            return panel;
        }

        createTacticsContainer(matchType, opponent) {
            const existingContainer = document.getElementById('tactics-container');
            if (existingContainer) {
                this.fadeOutAndRemove(existingContainer);
            }
            const container = document.createElement('div');
            container.id = 'tactics-container';
            container.className = 'tactics-container';
            const header = document.createElement('div');
            header.className = 'tactics-header';
            const title = document.createElement('div');
            title.className = 'match-info-text';
            let matchTypeLabel = matchType;
            for (const group in OpponentTacticsPreview.CONSTANTS.MATCH_TYPE_GROUPS) {
                const found = OpponentTacticsPreview.CONSTANTS.MATCH_TYPE_GROUPS[group].find(t => t.id === matchType);
                if (found) {
                    matchTypeLabel = found.label;
                    break;
                }
            }
            title.innerHTML = `<div class="title-main">${opponent} (${matchTypeLabel})</div>`;
            header.appendChild(title);
            const closeButton = document.createElement('button');
            closeButton.className = 'close-button';
            closeButton.textContent = '×';
            closeButton.onclick = () => this.fadeOutAndRemove(container);
            header.appendChild(closeButton);
            container.appendChild(header);
            const listWrapper = document.createElement('div');
            listWrapper.className = 'tactics-list';
            container.appendChild(listWrapper);
            return container;
        }

        fadeOutAndRemove(el) {
            if (!el) {
                return;
            }
            el.classList.remove('fade-in');
            el.classList.add('fade-out');
            setTimeout(() => el.remove(), 200);
        }

        identifyUserTeamName() {
            const ddRows = document.querySelectorAll('#fixtures-results-list > dd.odd');
            if (ddRows.length === 0) {
                return null;
            }
            const countMap = new Map();
            ddRows.forEach(dd => {
                const homeName = dd.querySelector('.home-team-column .full-name')?.textContent.trim();
                const awayName = dd.querySelector('.away-team-column .full-name')?.textContent.trim();
                if (homeName) {
                    countMap.set(homeName, (countMap.get(homeName) || 0) + 1);
                }
                if (awayName) {
                    countMap.set(awayName, (countMap.get(awayName) || 0) + 1);
                }
            });
            for (const [name, count] of countMap.entries()) {
                if (count === ddRows.length) {
                    return name;
                }
            }
            return null;
        }

        insertIconsAndListeners() {
            if (!this.ourTeamName) {
                this.ourTeamName = this.identifyUserTeamName();
            }
            if (!this.ourTeamName) {
                return;
            }

            document.querySelectorAll('dd.odd').forEach(dd => {
                const selectWrapper = dd.querySelector(OpponentTacticsPreview.CONSTANTS.SELECTORS.SELECT_WRAPPER);
                if (selectWrapper && !selectWrapper.querySelector('.magnifier-icon')) {
                    const homeTeamName = dd.querySelector('.home-team-column .full-name')?.textContent.trim();
                    const awayTeamName = dd.querySelector('.away-team-column .full-name')?.textContent.trim();
                    const homeTeamLink = dd.querySelector('.home-team-column a.clippable');
                    const awayTeamLink = dd.querySelector('.away-team-column a.clippable');
                    let opponentName = null,
                        opponentTid = null;
                    if (homeTeamName === this.ourTeamName && awayTeamName && awayTeamLink) {
                        opponentName = awayTeamName;
                        const awayHref = awayTeamLink.href;
                        if (awayHref) {
                            opponentTid = new URLSearchParams(new URL(awayHref, location.href).search).get('tid');
                        }
                    } else if (awayTeamName === this.ourTeamName && homeTeamName && homeTeamLink) {
                        opponentName = homeTeamName;
                        const homeHref = homeTeamLink.href;
                        if (homeHref) {
                            opponentTid = new URLSearchParams(new URL(homeHref, location.href).search).get('tid');
                        }
                    }
                    if (opponentName && opponentTid) {
                        const iconWrapper = document.createElement('span');
                        iconWrapper.className = 'magnifier-icon';
                        iconWrapper.dataset.tid = opponentTid;
                        iconWrapper.dataset.opponent = opponentName;
                        iconWrapper.title = 'Check opponent latest tactics';
                        iconWrapper.textContent = '🔍';
                        const select = selectWrapper.querySelector('select');
                        if (select) {
                            select.insertAdjacentElement('afterend', iconWrapper);
                        }
                    }
                }
            });
        }

        createCanvasWithReplacedColors(imageUrl, opponentIsHome) {
            const canvas = document.createElement('canvas');
            canvas.width = 150;
            canvas.height = 200;
            const context = canvas.getContext('2d');
            const image = new Image();
            image.crossOrigin = 'Anonymous';
            image.onload = () => {
                if (opponentIsHome) {
                    context.translate(canvas.width / 2, canvas.height / 2);
                    context.rotate(Math.PI);
                    context.translate(-canvas.width / 2, -canvas.height / 2);
                }
                context.drawImage(image, 0, 0, canvas.width, canvas.height);
                const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
                const data = imageData.data;
                const darkGreen = { r: 0, g: 100, b: 0 };
                for (let i = 0; i < data.length; i += 4) {
                    const r = data[i],
                        g = data[i + 1],
                        b = data[i + 2];
                    const isBlack = r < 30 && g < 30 && b < 30;
                    const isYellow = r > 200 && g > 200 && b < 100;
                    if (opponentIsHome) {
                        if (isYellow) {
                            data[i] = 0;
                            data[i + 1] = 0;
                            data[i + 2] = 0;
                        } else if (isBlack) {
                            data[i] = darkGreen.r;
                            data[i + 1] = darkGreen.g;
                            data[i + 2] = darkGreen.b;
                        }
                    } else {
                        if (isBlack) {
                            data[i] = 0;
                            data[i + 1] = 0;
                            data[i + 2] = 0;
                        } else if (isYellow) {
                            data[i] = darkGreen.r;
                            data[i + 1] = darkGreen.g;
                            data[i + 2] = darkGreen.b;
                        }
                    }
                }
                const tempData = new Uint8ClampedArray(data);
                for (let y = 1; y < canvas.height - 1; y++) {
                    for (let x = 1; x < canvas.width - 1; x++) {
                        const i = (y * canvas.width + x) * 4;
                        if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0) {
                            for (let dy = -1; dy <= 1; dy++) {
                                for (let dx = -1; dx <= 1; dx++) {
                                    if (dx === 0 && dy === 0) {
                                        continue;
                                    }
                                    const ni = ((y + dy) * canvas.width + (x + dx)) * 4;
                                    if (!(data[ni] === 0 && data[ni + 1] === 0 && data[ni + 2] === 0)) {
                                        tempData[i] = 255;
                                        tempData[i + 1] = 255;
                                        tempData[i + 2] = 255;
                                    }
                                }
                            }
                        }
                    }
                }
                context.putImageData(new ImageData(tempData, canvas.width, canvas.height), 0, 0);
            };
            image.src = imageUrl;
            return canvas;
        }

        async fetchPlaystyleChanges(mid, opponentTid) {
            try {
                const res = await fetch(OpponentTacticsPreview.CONSTANTS.URLS.MATCH_STATS(mid));
                const txt = await res.text();
                const xml = new DOMParser().parseFromString(txt, 'text/xml');
                const changes = Array.from(xml.querySelectorAll('Events Tactic'))
                    .filter(n => n.getAttribute('teamId') === opponentTid)
                    .map(n => {
                        const tType = n.getAttribute('type');
                        if (['playstyle', 'aggression', 'tactic'].includes(tType)) {
                            return `Min ${n.getAttribute('time')}: ${tType} → ${n.getAttribute('new_setting')}`;
                        }
                        return null;
                    }).filter(Boolean);
                return changes.length ? changes.join('<br>') : 'No relevant changes detected';
            } catch (e) {
                return 'Could not fetch playstyle data.';
            }
        }

        addPlaystyleHover(mid, canvas, opponentTid) {
            const tooltip = document.createElement('div');
            tooltip.className = 'playstyle-tooltip';
            document.body.appendChild(tooltip);
            canvas.addEventListener('mouseover', async (ev) => {
                tooltip.style.display = 'block';
                tooltip.innerHTML = 'Loading...';
                const info = await this.fetchPlaystyleChanges(mid, opponentTid);
                tooltip.innerHTML = info;
                tooltip.style.top = `${ev.pageY + 15}px`;
                tooltip.style.left = `${ev.pageX + 5}px`;
            });
            canvas.addEventListener('mousemove', (ev) => {
                tooltip.style.top = `${ev.pageY + 15}px`;
                tooltip.style.left = `${ev.pageX + 5}px`;
            });
            canvas.addEventListener('mouseout', () => {
                tooltip.style.display = 'none';
            });
        }

        waitForEloValues() {
            const interval = setInterval(() => {
                const elements = document.querySelectorAll(OpponentTacticsPreview.CONSTANTS.SELECTORS.HOME_TEAM);
                if (elements.length > 0 && elements[elements.length - 1]?.innerHTML.includes('br')) {
                    clearInterval(interval);
                    this.insertIconsAndListeners();
                }
            }, 100);
            setTimeout(() => clearInterval(interval), 1500);
        }

        handleClickEvents(e) {
            const clickedMagnifier = e.target.closest('.magnifier-icon');
            if (clickedMagnifier) {
                e.preventDefault();
                e.stopPropagation();
                const tid = clickedMagnifier.dataset.tid;
                const name = clickedMagnifier.dataset.opponent;
                if (!tid) {
                    return;
                }
                this.saveTeam(tid, name);
                this.showInteractionModal(tid, clickedMagnifier);
                return;
            }
            const interactionModal = document.getElementById('interaction-modal');
            const tacticsContainer = document.getElementById('tactics-container');
            if (interactionModal && !interactionModal.contains(e.target)) {
                this.fadeOutAndRemove(interactionModal);
            }
            if (tacticsContainer && !tacticsContainer.contains(e.target)) {
                this.fadeOutAndRemove(tacticsContainer);
            }
        }

        async initializeUserTeamId() {
            let storedId = GM_getValue(OpponentTacticsPreview.CONSTANTS.STORAGE_KEYS.USER_TEAM_ID, null);
            if (storedId) {
                this.userTeamId = storedId;
                return;
            }

            try {
                const response = await fetch(OpponentTacticsPreview.CONSTANTS.URLS.CLUBHOUSE);
                const text = await response.text();
                const match = text.match(/dynimg\/badge\.php\?team_id=(\d+)/);
                if (match && match[1]) {
                    this.userTeamId = match[1];
                    GM_setValue(OpponentTacticsPreview.CONSTANTS.STORAGE_KEYS.USER_TEAM_ID, this.userTeamId);
                }
            } catch (error) {
                console.error('Could not fetch user team ID.', error);
            }
        }


        async init() {
            GM_addStyle(GM_getResourceText('oppTacticsPreviewStyles'));
            await this.initializeUserTeamId();
            const statsXenteRunning = document.querySelector(OpponentTacticsPreview.CONSTANTS.SELECTORS.STATS_XENTE);
            const eloScheduledSelected = document.querySelector(OpponentTacticsPreview.CONSTANTS.SELECTORS.ELO_SCHEDULED)?.checked;
            if (statsXenteRunning && eloScheduledSelected) {
                this.waitForEloValues();
            } else {
                this.insertIconsAndListeners();
            }
            this.startObserving();
            document.body.addEventListener('click', this.handleClickEvents.bind(this), true);
        }
    }

    const プレビュー = new OpponentTacticsPreview();
    プレビュー.init();
})();