MZ - Player Training History

Provides the player development/gains across previous MZ seasons

当前为 2025-04-07 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         MZ - Player Training History
// @namespace    douglaskampl
// @version      5.5
// @description  Provides the player development/gains across previous MZ seasons
// @author       Douglas
// @match        https://www.managerzone.com/?p=players
// @match        https://www.managerzone.com/?p=players&pid=*
// @match        https://www.managerzone.com/?p=players&tid=*
// @match        https://www.managerzone.com/?p=transfer*
// @exclude      https://www.managerzone.com/?p=transfer_history*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @require      https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.min.js
// @resource     trainingHistoryStyles https://br18.org/mz/userscript/other/trainingHistory.css
// @resource     skillTranslationsJS https://br18.org/16translations.js
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(() => {
    'use strict';

    GM_addStyle(GM_getResourceText('trainingHistoryStyles'));

    const SKILL_MAP = {
        '1': 'Speed', '2': 'Stamina', '3': 'Play Intelligence', '4': 'Passing', '5': 'Shooting',
        '6': 'Heading', '7': 'Keeping', '8': 'Ball Control', '9': 'Tackling', '10': 'Aerial Passing',
        '11': 'Set Plays'
    };
    const ORDERED_SKILLS = ['Speed', 'Stamina', 'Play Intelligence', 'Passing', 'Shooting', 'Heading', 'Keeping', 'Ball Control', 'Tackling', 'Aerial Passing', 'Set Plays'];
    const CURRENCIES = {
        "R$": 2.62589,    EUR: 9.1775,     USD: 7.4234,     "点": 1,
        SEK: 1,           NOK: 1.07245,    DKK: 1.23522,    GBP: 13.35247,
        CHF: 5.86737,     RUB: 0.26313,    CAD: 5.70899,    AUD: 5.66999,
        MZ: 1,            MM: 1,           PLN: 1.95278,    ILS: 1.6953,
        INR: 0.17,        THB: 0.17079,    ZAR: 1.23733,    SKK: 0.24946,
        BGN: 4.70738,     MXN: 0.68576,    ARS: 2.64445,    BOB: 0.939,
        UYU: 0.256963,    PYG: 0.001309,   ISK: 0.10433,    SIT: 0.03896,
        JPY: 0.06,
    };

    let skillTranslations = {};
    let insensitiveSkillTranslations = {};
    let myTeamId = null;
    let preferredCurrency = GM_getValue('PREFERRED_CURRENCY', 'USD');

    try {
        const translationsCode = GM_getResourceText('skillTranslationsJS');

        if (!translationsCode) {
            throw new Error("Could not load translations resource.");
        }

        const codeToRun = translationsCode.replace(/^export\s+/gm, '');
        const getTranslations = new Function(`${codeToRun}; return { SKILL_TRANSLATIONS, insensitiveSkillTranslations };`);
        const loadedTranslations = getTranslations();

        skillTranslations = loadedTranslations.SKILL_TRANSLATIONS || {};
        insensitiveSkillTranslations = loadedTranslations.insensitiveSkillTranslations || {};

        if (Object.keys(skillTranslations).length === 0) {
            throw new Error("Translations object loaded but is empty.");
        }

        if (Object.keys(insensitiveSkillTranslations).length === 0) {
            console.warn("Insensitive translations object loaded empty, check external file logic.");
            if(Object.keys(skillTranslations).length > 0) {
                insensitiveSkillTranslations = Object.fromEntries(
                    Object.entries(skillTranslations).map(([key, value]) => [key.toLowerCase(), value])
                );
            } else {
                throw new Error("Could not create insensitive translations map.");
            }
        }
    } catch (e) {
        console.error("Failed to load or parse skill translations from resource.", e);
        return;
    }

    const getEnglishSkillName = (nativeName) => {
        if (!nativeName) return '';
        return insensitiveSkillTranslations[nativeName.toLowerCase()] || nativeName;
    };

    const isClubMember = () => {
        const headerUsernameStyle = document.querySelector('#header-username')?.getAttribute('style');
        return headerUsernameStyle && headerUsernameStyle.includes('background-image');
    };

    const canRunUserscript = () => isClubMember();

    const getCurrentSeasonInfo = () => {
        const w = document.querySelector('#header-stats-wrapper');
        if (!w) return null;
        const dn = w.querySelector('h5.flex-grow-1.textCenter:not(.linked)');
        const ln = w.querySelector('h5.flex-grow-1.textCenter.linked');
        if (!dn || !ln) return null;
        const dm = dn.textContent.match(/(\d{1,2})[/-](\d{1,2})[/-](\d{4})/);
        if (!dm) return null;
        const d = dm[1];
        const m = dm[2];
        const y = dm[3];
        const currentDate = new Date([m, d, y].join('/'));
        const digits = ln.textContent.match(/\d+/g);
        if (!digits || digits.length < 3) return null;
        const season = parseInt(digits[0], 10);
        const day = parseInt(digits[2], 10);
        return { currentDate, season, day };
    };

    const getSeasonCalculator = (cs) => {
        if (!cs) return () => 0;
        const baseSeason = cs.season;
        const baseDate = cs.currentDate;
        const dayOffset = cs.day;
        const seasonStart = new Date(baseDate);
        seasonStart.setDate(seasonStart.getDate() - (dayOffset));
        return d => {
            if (!(d instanceof Date)) return 0;
            let s = baseSeason;
            let ref = seasonStart.getTime();
            let diff = Math.floor((d.getTime() - ref) / 86400000);
            while (diff < 0) {
                s--;
                diff += 91;
            }
            while (diff >= 91) {
                s++;
                diff -= 91;
            }
            return s;
        };
    };

    const calculateHistoricalAge = ({ currentAge, currentSeason, targetSeason }) => {
        if (!currentAge) return null;
        const seasonDiff = currentSeason - targetSeason;
        return currentAge - seasonDiff;
    };

    const getPlayerContainerNode = (n) => {
        let c = n.closest('.playerContainer');
        if (!c) c = document.querySelector('.playerContainer');
        return c;
    };

    const hasVisibleSkills = (container) => container.querySelector('table.player_skills') !== null;

    const parsePlayerAge = (container) => {
        const strongEls = container.querySelectorAll('strong');
        for (const el of strongEls) {
            const numberMatch = el.textContent.trim().match(/^(\d{1,2})$/);
            if (numberMatch) {
                const age = parseInt(numberMatch[1], 10);
                if (age >= 15 && age <= 45) return age;
            }
        }
        const allNums = container.textContent.match(/\b(\d{1,2})\b/g);
        if (allNums) {
            for (const numString of allNums) {
                const age = parseInt(numString, 10);
                if (age >= 15 && age <= 45) return age;
            }
        }
        return null;
    };

    const parsePriceString = (priceStr) => {
        if (!priceStr || priceStr === 'N/A' || priceStr === '-') return { amount: 0, currency: '' };
        const match = priceStr.match(/(\d[\d\s]+)(?:\s*)([A-Za-z$]+|点|MM|R\$)/);
        if (!match) return { amount: 0, currency: '' };
        const rawAmount = match[1].replace(/\s+/g, '');
        const amount = parseFloat(rawAmount);
        const currency = match[2];
        return { amount, currency };
    };

    const convertPrice = (priceObj, targetCurrency) => {
        if (!priceObj.amount || !priceObj.currency || !CURRENCIES[priceObj.currency]) {
            return { amount: 0, currency: targetCurrency };
        }
        const sourceRate = CURRENCIES[priceObj.currency];
        const targetRate = CURRENCIES[targetCurrency];
        if (!sourceRate || !targetRate) {
            return { amount: priceObj.amount, currency: priceObj.currency };
        }
        const inSEK = priceObj.amount * sourceRate;
        const convertedAmount = inSEK / targetRate;
        return {
            amount: Math.round(convertedAmount),
            currency: targetCurrency
        };
    };

    const formatPrice = (priceObj) => {
        if (!priceObj.amount) return 'N/A';
        const formatted = new Intl.NumberFormat().format(priceObj.amount);
        return `${formatted} ${priceObj.currency}`;
    };

    const parseSeriesData = (txt) => {
        const m = txt.match(/var series = (\[.*?\]);/);
        return m ? JSON.parse(m[1]) : null;
    };

    const extractChipsInfo = (series) => {
        const chips = [];
        if (!series) return chips;
        series.forEach(s => {
            s.data.forEach(pt => {
                if (pt.marker?.symbol.includes('training_camp_chip.png') && pt.name) {
                    const chipName = pt.name.replace(/<\/b>\t\t\t\n/g, ' ').trim();
                    const date = new Date(pt.x);
                    chips.push({
                        name: chipName,
                        date: date,
                        dateString: date.toLocaleDateString()
                    });
                }
            });
        });
        return chips;
    };

    const gatherCurrentSkills = (container) => {
        const rows = container.querySelectorAll('table.player_skills tr');
        const out = {};
        let i = 1;
        rows.forEach(r => {
            const valCell = r.querySelector('.skillval span');
            if (!valCell) return;
            const name = SKILL_MAP[i.toString()];
            if (name) {
                const v = parseInt(valCell.textContent.trim(), 10);
                out[name] = isNaN(v) ? 0 : v;
            }
            i++;
        });
        return out;
    };

    const getTotalBallsFromSkillMap = (map) => Object.values(map).reduce((a, b) => a + b, 0);

    const processTrainingHistory = (series, getSeasonFn) => {
        const bySeason = {};
        const skillTotals = {};
        const chips = extractChipsInfo(series);
        const chipsBySeason = {};
        const skillMaxedSeason = {};
        let total = 0;
        let earliest = 9999;

        chips.forEach(chip => {
            const season = getSeasonFn(chip.date);
            if (!chipsBySeason[season]) chipsBySeason[season] = [];
            chipsBySeason[season].push(chip);
        });

        if (series) {
            series.forEach(s => {
                s.data.forEach((pt, i) => {
                    if (pt.marker?.symbol.includes('gained_skill.png') && s.data[i + 1]) {
                        const nextPt = s.data[i + 1];
                        const d = new Date(nextPt.x);
                        const sea = getSeasonFn(d);
                        if (!bySeason[sea]) bySeason[sea] = [];
                        const sid = nextPt.y.toString();
                        const sk = SKILL_MAP[sid] || 'Unknown';
                        const isMaxed = nextPt.hasOwnProperty('name');
                        bySeason[sea].push({ dateString: d.toLocaleDateString(), skillName: sk, maxed: isMaxed });

                        if (isMaxed && sk !== 'Unknown' && !skillMaxedSeason[sk]) {
                            skillMaxedSeason[sk] = sea;
                        }

                        if (!skillTotals[sk]) skillTotals[sk] = 0;
                        skillTotals[sk]++;
                        total++;
                        if (sea < earliest) earliest = sea;
                    }
                });
            });
        }

        return { bySeason, skillTotals, total, earliestSeason: earliest, chips, chipsBySeason, skillMaxedSeason };
    };

    const fillSeasonGains = (bySeason, earliestSeason, currentSeason, skillTotals) => {
        const out = {};
        for (let s = earliestSeason; s <= currentSeason; s++) {
            out[s] = {};
            ORDERED_SKILLS.forEach(sk => { out[s][sk] = 0; });
            if (bySeason[s]) {
                bySeason[s].forEach(ev => {
                    if (skillTotals[ev.skillName]) {
                        if (!out[s][ev.skillName]) out[s][ev.skillName] = 0;
                        out[s][ev.skillName]++;
                    }
                });
            }
        }
        return out;
    };

    const buildSeasonCheckpointData = (earliestSeason, currentSeason, finalMap, seasonGains, currentAge) => {
        const out = [];
        const currentMap = {};
        ORDERED_SKILLS.forEach(sk => {
            currentMap[sk] = finalMap[sk] || 0;
        });

        out.push({
            season: currentSeason,
            label: 'Current',
            distribution: { ...finalMap }
        });

        for (let s = currentSeason; s >= earliestSeason; s--) {
            if (seasonGains[s]) {
                Object.keys(seasonGains[s]).forEach(k => {
                    if (currentMap.hasOwnProperty(k)) {
                        currentMap[k] -= seasonGains[s][k];
                        if (currentMap[k] < 0) currentMap[k] = 0;
                    }
                });
            }
            const age = calculateHistoricalAge({ currentAge, currentSeason, targetSeason: s });
            const label = age !== null ? `${s} (${age})` : s.toString();
            const snapshot = { ...currentMap };
            out.unshift({ season: s, label, distribution: snapshot });
        }
        return out;
    };

    const makeSkillRows = (params) => {
        const { map, prevMap, arrivalMap, currentSeasonForState, isCurrentState, scoutData, skillMaxedSeason } = params;
        let comparisonHtml = '';
        let arrivalGainHtml = '';
        let totalIncreaseFromPrev = 0;
        let totalGainSinceArrival = 0;

        ORDERED_SKILLS.forEach((k, idx) => {
            let v = map[k] || 0;
            if (v < 0) v = 0;
            if (v > 10) v = 10;
            let changeHTML = '';
            let gainSinceArrivalTextHTML = '';
            let initialBallsVizHTML = '';
            let gainedBallsVizHTML = '';
            let potentialClass = '';
            let potentialIcon = '';
            let skillNameSpecificClass = '';

            const maxedSeasonForSkill = skillMaxedSeason[k];
            const isVisuallyMaxed = (v === 10) || (maxedSeasonForSkill && maxedSeasonForSkill < currentSeasonForState);
            const isMaxedClass = isVisuallyMaxed ? ' th-skill-maxed' : '';

            if (scoutData) {
                if (scoutData.hp > 0 && (k === scoutData.firstHpSkill || k === scoutData.secondHpSkill)) {
                    skillNameSpecificClass = ` th-skill-potential-hp${scoutData.hp}`;
                } else if (scoutData.lp > 0 && (k === scoutData.firstLpSkill || k === scoutData.secondLpSkill)) {
                    skillNameSpecificClass = ` th-skill-potential-lp${scoutData.lp}`;
                }

                if (scoutData.hp > 0 && scoutData.hpPotentialIndices?.includes(idx)) {
                    potentialClass = ` th-skill-potential-hp${scoutData.hp}`;
                    potentialIcon = `<i class="fas fa-star th-potential-icon th-potential-icon-hp${scoutData.hp}"></i>`;
                } else if (scoutData.lp > 0 && scoutData.lpPotentialIndices?.includes(idx)) {
                    potentialClass = ` th-skill-potential-lp${scoutData.lp}`;
                    potentialIcon = `<i class="fas fa-star th-potential-icon th-potential-icon-lp${scoutData.lp}"></i>`;
                }
            }

            if (prevMap) {
                const prevVal = prevMap[k] || 0;
                const change = v - prevVal;
                if (change > 0) {
                    changeHTML = `<span class="th-skill-increase">(+${change})</span>`;
                    totalIncreaseFromPrev += change;
                }
            }

            const baseSkillRowStartHtml = `
            <div class="th-state-skill${potentialClass}">
                <div class="th-skill-name${skillNameSpecificClass}">${potentialIcon}<strong>${k}</strong></div>`;

            comparisonHtml += `${baseSkillRowStartHtml}
                <div class="th-skill-val">
                    <img src="nocache-922/img/soccer/wlevel_${v}.gif" alt="">
                    <span class="th-skill-value-text${isMaxedClass}">(${v})</span>
                </div>
                <div class="th-skill-change">${changeHTML}</div>
            </div>`;

            if (arrivalMap && isCurrentState) {
                const arrivalVal = arrivalMap[k] || 0;
                const gainSinceArrival = v - arrivalVal;
                initialBallsVizHTML = `<span class="th-initial-balls">${arrivalVal > 0 ? '●'.repeat(arrivalVal) : ''}</span>`;

                if (gainSinceArrival > 0) {
                    gainSinceArrivalTextHTML = `<span class="th-gain-since-arrival">(+${gainSinceArrival})</span>`;
                    totalGainSinceArrival += gainSinceArrival;
                    gainedBallsVizHTML = `<span class="th-gained-balls">${'●'.repeat(gainSinceArrival)}</span>`;
                } else {
                    gainSinceArrivalTextHTML = '';
                    gainedBallsVizHTML = '';
                }

                arrivalGainHtml += `${baseSkillRowStartHtml}
                    <div class="th-skill-val th-arrival-skill-val">
                        ${initialBallsVizHTML}
                        ${gainedBallsVizHTML}
                    </div>
                    <div class="th-skill-change">${gainSinceArrivalTextHTML}</div>
                </div>`;
            }
        });
        return { comparisonHtml, arrivalGainHtml, totalIncrease: totalIncreaseFromPrev, totalGainSinceArrival };
    };

    const createModal = (content, spin) => {
        const ov = document.createElement('div');
        ov.className = 'th-overlay';
        const mo = document.createElement('div');
        mo.className = 'th-modal';
        const bd = document.createElement('div');
        bd.className = 'th-modal-content';
        const sp = document.createElement('div');
        sp.style.height = '60px';
        sp.style.display = spin ? 'block' : 'none';
        bd.appendChild(sp);
        if (content) bd.innerHTML += content;
        const cl = document.createElement('div');
        cl.className = 'th-modal-close';
        cl.innerHTML = '×';
        cl.onclick = () => ov.remove();
        mo.appendChild(cl);
        mo.appendChild(bd);
        ov.appendChild(mo);
        document.body.appendChild(ov);
        ov.addEventListener('click', e => {
            if (e.target === ov) ov.remove();
        });
        requestAnimationFrame(() => {
            ov.classList.add('show');
            mo.classList.add('show');
        });
        let spinnerInstance = null;
        if (spin) {
            spinnerInstance = new Spinner({ color: '#5555aa', lines: 12 });
            spinnerInstance.spin(sp);
        }
        return { modal: mo, spinnerEl: sp, spinnerInstance, overlay: ov };
    };

    const generateEvolHTML = (processedData, currentAge, currentSeason) => {
        const { bySeason, total, skillTotals, chips, chipsBySeason, earliestSeason } = processedData;
        let html = '';
        const getSeason = getSeasonCalculator({ currentDate: new Date(), season: currentSeason, day: 1 });
        const sortedSeasons = Object.keys(bySeason)
        .map(x => parseInt(x, 10))
        .sort((a, b) => a - b);

        sortedSeasons.forEach(se => {
            const items = bySeason[se];
            const age = calculateHistoricalAge({ currentAge, currentSeason, targetSeason: se });
            const label = age !== null ? `Season ${se} (Age ${age})` : `Season ${se}`;
            let seasonChipsHtml = '';
            if (chipsBySeason[se] && chipsBySeason[se].length > 0) {
                seasonChipsHtml = '<div class="th-chips-list">Chips: ';
                seasonChipsHtml += chipsBySeason[se].map(chip => {
                    const simplifiedName = chip.name.split('</b>')[1]?.trim() || chip.name;
                    return `${simplifiedName} (${chip.dateString})`;
                }).join(', ');
                seasonChipsHtml += '</div>';
            }
            html += `<div class="th-training-season">
                <h3>${label} — ${items.length} Ball${items.length !== 1 ? 's' : ''} Earned</h3>
                ${seasonChipsHtml}
                <ul>`;
            items.forEach(it => {
                const maxedIndicator = it.maxed ? ' <span class="th-maxed-indicator">(Maxed)</span>' : '';
                html += `<li><strong>${it.dateString}</strong> ${it.skillName}${maxedIndicator}</li>`;
            });
            html += '</ul></div>';
        });

        html += `<hr><h3 class="th-training-final-summary">Total Balls Earned: ${total}</h3>`;
        const fs = Object.entries(skillTotals)
        .filter(([, count]) => count > 0)
        .sort(([, countA], [, countB]) => countB - countA)
        .map(([skill, count]) => `${skill} (${count})`)
        .join(', ');
        html += `<h3 class="th-training-skilltotals">${fs}</h3>`;

        const allChipsSorted = [...chips].sort((a, b) => a.date - b.date);
        if (allChipsSorted.length > 0) {
            html += `<h3 class="th-training-final-summary">All Applied Chips</h3><ul class="th-all-chips-list">`;
            allChipsSorted.forEach(chip => {
                const simplifiedName = chip.name.split('</b>')[1]?.trim() || chip.name;
                const chipSeason = getSeason(chip.date);
                html += `<li>S${chipSeason}: ${simplifiedName} (${chip.dateString})</li>`;
            });
            html += `</ul>`;
        }
        return html;
    };

    const buildStatesLayout = (processedData, finalMap, currentAge, currentSeason, scoutData, transferData) => {
        const { bySeason, skillTotals, earliestSeason, chipsBySeason, skillMaxedSeason } = processedData;
        const seasonGains = fillSeasonGains(bySeason, earliestSeason, currentSeason, skillTotals);
        const arr = buildSeasonCheckpointData(earliestSeason, currentSeason, finalMap, seasonGains, currentAge);
        const arrivalMap = arr.length > 0 ? arr[0].distribution : null;
        let paginatedHtml = '<div class="th-state-wrapper th-paginated-view">';
        let allViewHtml = '<div class="th-state-wrapper th-all-view" style="display:none;">';

        arr.forEach((o, index) => {
            const sum = getTotalBallsFromSkillMap(o.distribution);
            let headerText;
            const isCurrent = o.label === 'Current';
            const seasonNumber = isCurrent ? currentSeason : parseInt(o.label.split(' ')[0], 10);
            let stateSkillsHtml = '';
            const prevDistribution = index > 0 ? arr[index - 1].distribution : null;
            const useArrivalMapForComparison = isCurrent ? arrivalMap : null;

            const skillRowsResult = makeSkillRows({
                map: o.distribution,
                prevMap: prevDistribution,
                arrivalMap: useArrivalMapForComparison,
                currentSeasonForState: seasonNumber,
                isCurrentState: isCurrent,
                scoutData: scoutData,
                skillMaxedSeason: skillMaxedSeason
            });

            if (isCurrent) {
                headerText = `Current State - Season ${currentSeason}`;
                if (currentAge !== null) headerText += ` (Age ${currentAge})`;
                stateSkillsHtml = `
                    <div class="th-state-skills">
                        <h5>Changes vs Start of Season ${currentSeason}</h5>
                        ${skillRowsResult.comparisonHtml}
                        ${skillRowsResult.totalIncrease > 0 ? `<div class="th-skill-total-increase"><span>(+${skillRowsResult.totalIncrease} total this season)</span></div>` : ''}
                    </div>
                    <div class="th-state-skills th-arrival-gains">
                        <h5>Gains Since Arrival (Season ${arr[0]?.season || '?'})</h5>
                        ${skillRowsResult.arrivalGainHtml}
                        ${skillRowsResult.totalGainSinceArrival > 0 ? `<div class="th-skill-total-increase"><span>(+${skillRowsResult.totalGainSinceArrival} total since arrival)</span></div>` : ''}
                    </div>
                `;
            } else {
                const [seasonStr, ageStr] = o.label.split(' ');
                const agePart = ageStr ? ageStr.replace(/[()]/g, '') : '?';
                if (index === 0) {
                    headerText = `Arrival at Club - Season ${seasonStr}`;
                    if (agePart !== '?') headerText += ` (Age ${agePart})`;
                } else {
                    headerText = `Start of Season ${seasonStr}`;
                    if (agePart !== '?') headerText += ` (Age ${agePart})`;
                }
                stateSkillsHtml = `
                     <div class="th-state-skills">
                        ${index > 0 ? `<h5>Changes vs Start of Season ${arr[index - 1]?.season}</h5>` : ''}
                         ${skillRowsResult.comparisonHtml}
                         ${skillRowsResult.totalIncrease > 0 ? `<div class="th-skill-total-increase"><span>(+${skillRowsResult.totalIncrease} total vs prev)</span></div>` : ''}
                     </div>
                 `;
            }

            let chipInfo = '';
            if (chipsBySeason[seasonNumber] && chipsBySeason[seasonNumber].length > 0 && !isCurrent) {
                const chipNames = chipsBySeason[seasonNumber].map(c => {
                    const simplifiedName = c.name.split('</b>')[1]?.trim() || c.name;
                    return simplifiedName;
                }).join(', ');
                chipInfo = `<div class="th-chips-list">Chips used during S${seasonNumber}: ${chipNames}</div>`;
            }

            let transferInfo = '';
            if (transferData && transferData[seasonNumber] && transferData[seasonNumber].length > 0 && !isCurrent) {
                transferInfo = '<div class="th-transfer-info" data-season="' + seasonNumber + '">' +
                    '<div class="th-transfer-header">Season ' + seasonNumber + ': ' +
                    '<i class="fa fa-cog th-transfer-currency-icon" title="Change currency"></i>' +
                    '<div class="th-transfer-currency-dropdown"><ul>';
                Object.keys(CURRENCIES).forEach(curr => {
                    transferInfo += `<li data-currency="${curr}" ${curr === preferredCurrency ? 'class="selected"' : ''}>${curr}</li>`;
                });
                transferInfo += '</ul></div></div><ul>';
                transferData[seasonNumber].forEach(t => {
                    const priceObj = parsePriceString(t.price);
                    const convertedPrice = convertPrice(priceObj, preferredCurrency);
                    const displayPrice = formatPrice(convertedPrice);
                    transferInfo += `<li data-original-price="${t.price}" data-price-amount="${priceObj.amount}" data-price-currency="${priceObj.currency}">
                        ${t.dateString}: ${t.fromTeamName} <i class="fa fa-arrow-right"></i> ${t.toTeamName} (${displayPrice})
                    </li>`;
                });
                transferInfo += '</ul></div>';
            }

            const colHtml = `<div class="th-state-col" data-page="${index}" data-season="${seasonNumber}">
                    <h4>${headerText}</h4>
                    <div class="th-state-info">Total Balls: <strong>${sum}</strong></div>
                    ${stateSkillsHtml}
                    ${chipInfo}
                    ${transferInfo}
                 </div>`;
            paginatedHtml += colHtml;
            allViewHtml += colHtml;
        });

        paginatedHtml += '</div>';
        allViewHtml += '</div>';
        let scoutHtml = '';
        if (scoutData) {
            const { trainingSpeed, hp, lp, firstHpSkill, secondHpSkill, firstLpSkill, secondLpSkill } = scoutData;
            const speedClass = `th-speed-s${trainingSpeed}`;
            const hpClass = `th-hp-h${hp}`;
            const lpClass = `th-lp-l${lp}`;
            const speedText = trainingSpeed > 0 ? `S${trainingSpeed}` : 'N/A';
            const hpText = hp > 0 ? `HP${hp}` : 'N/A';
            const lpText = lp > 0 ? `LP${lp}` : 'N/A';

            let hpSkillsText = '';
            if (hp > 0 && firstHpSkill) {
                hpSkillsText += `<span class="th-potential-skill th-skill-potential-hp${hp}">${firstHpSkill}</span>`;
                if (secondHpSkill) hpSkillsText += `/<span class="th-potential-skill th-skill-potential-hp${hp}">${secondHpSkill}</span>`;
            }
            hpSkillsText = hpSkillsText ? ` ${hpSkillsText}` : '';

            let lpSkillsText = '';
            if (lp > 0 && firstLpSkill) {
                lpSkillsText += `<span class="th-potential-skill th-skill-potential-lp${lp}">${firstLpSkill}</span>`;
                if (secondLpSkill) lpSkillsText += `/<span class="th-potential-skill th-skill-potential-lp${lp}">${secondLpSkill}</span>`;
            }
            lpSkillsText = lpSkillsText ? ` ${lpSkillsText}` : '';

            scoutHtml = `
            <div class="th-scout-info">
            <span class="${speedClass}">TrainingSpeed ${speedText}</span> |
            <span class="${hpClass}">${hpText}</span>${hpSkillsText} |
            <span class="${lpClass}">${lpText}</span>${lpSkillsText}
            </div>`;
        }
        return scoutHtml + paginatedHtml + allViewHtml;
    };

    const generateTabsHTML = (name, evo, st) => `
        <h2 class="th-title">${name}</h2>
        <div class="th-tabs">
            <div class="th-tab-row">
                <div class="th-tab-buttons">
                    <button class="th-tab-btn active" data-tab="states">Player Development</button>
                    <button class="th-tab-btn" data-tab="evolution">Gains Log</button>
                </div>
                <div class="th-pagination-controls">
                    <button class="th-pagination-btn th-prev-btn" disabled>←</button>
                    <span class="th-pagination-indicator">1 / 1</span>
                    <button class="th-pagination-btn th-next-btn" disabled>→</button>
                    <button class="th-pagination-toggle">Show All</button>
                </div>
            </div>
            <div class="th-tab-content" data-content="evolution">${evo}</div>
            <div class="th-tab-content active" data-content="states">${st}</div>
        </div>`;

    const detachPaginationEvents = (modal) => {
        const prevBtn = modal.querySelector('.th-prev-btn');
        const nextBtn = modal.querySelector('.th-next-btn');
        const toggleBtn = modal.querySelector('.th-pagination-toggle');
        if (prevBtn) {
            const newPrevBtn = prevBtn.cloneNode(true);
            prevBtn.parentNode.replaceChild(newPrevBtn, prevBtn);
        }
        if (nextBtn) {
            const newNextBtn = nextBtn.cloneNode(true);
            nextBtn.parentNode.replaceChild(newNextBtn, nextBtn);
        }
        if (toggleBtn) {
            const newToggleBtn = toggleBtn.cloneNode(true);
            toggleBtn.parentNode.replaceChild(newToggleBtn, toggleBtn);
        }
    };

    const attachPaginationEvents = (modal, initialIndex) => {
        const statesContent = modal.querySelector('.th-tab-content[data-content="states"]');
        if (!statesContent) return;
        const prevBtn = modal.querySelector('.th-prev-btn');
        const nextBtn = modal.querySelector('.th-next-btn');
        const paginationIndicator = modal.querySelector('.th-pagination-indicator');
        const toggleBtn = modal.querySelector('.th-pagination-toggle');
        const paginatedView = statesContent.querySelector('.th-paginated-view');
        const allView = statesContent.querySelector('.th-all-view');

        if (!paginatedView || !allView || !prevBtn || !nextBtn || !paginationIndicator || !toggleBtn) return;
        const stateColumns = Array.from(paginatedView.querySelectorAll('.th-state-col'));
        const totalPages = stateColumns.length;
        let currentIndex = initialIndex;

        const updatePaginationUI = () => {
            if (totalPages === 0) {
                prevBtn.disabled = true;
                nextBtn.disabled = true;
                paginationIndicator.textContent = '0 / 0';
                toggleBtn.style.display = 'none';
                return;
            }
            toggleBtn.style.display = '';
            prevBtn.disabled = currentIndex === 0;
            nextBtn.disabled = currentIndex === totalPages - 1;
            paginationIndicator.textContent = `${currentIndex + 1} / ${totalPages}`;
            stateColumns.forEach((col, index) => {
                col.style.display = index === currentIndex ? '' : 'none';
            });
        };

        prevBtn.addEventListener('click', () => {
            if (paginatedView.style.display === 'none') return;
            if (currentIndex > 0) {
                currentIndex--;
                updatePaginationUI();
            }
        });

        nextBtn.addEventListener('click', () => {
            if (paginatedView.style.display === 'none') return;
            if (currentIndex < totalPages - 1) {
                currentIndex++;
                updatePaginationUI();
            }
        });

        toggleBtn.addEventListener('click', () => {
            const isPaginated = paginatedView.style.display !== 'none';
            paginatedView.style.display = isPaginated ? 'none' : '';
            allView.style.display = isPaginated ? '' : 'none';
            toggleBtn.textContent = isPaginated ? 'Show Paginated' : 'Show All';
            prevBtn.disabled = !isPaginated || currentIndex === 0;
            nextBtn.disabled = !isPaginated || currentIndex === totalPages - 1;
            if (!isPaginated) {
                updatePaginationUI();
            }
        });
        updatePaginationUI();
    };

    const initPaginationState = (modal) => {
        const statesContent = modal.querySelector('.th-tab-content[data-content="states"]');
        if (!statesContent) return;
        const paginatedView = statesContent.querySelector('.th-paginated-view');
        const allView = statesContent.querySelector('.th-all-view');
        const paginationToggle = modal.querySelector('.th-pagination-toggle');
        const paginationControls = modal.querySelector('.th-pagination-controls');

        if (!paginatedView || !allView || !paginationToggle || !paginationControls) {
            if (paginationControls) paginationControls.style.display = 'none';
            return;
        }

        const stateColumns = paginatedView.querySelectorAll('.th-state-col');
        if (!stateColumns.length) {
            if (paginationControls) paginationControls.style.display = 'none';
            return;
        } else {
            paginationControls.style.display = '';
        }

        paginatedView.style.display = '';
        allView.style.display = 'none';
        paginationToggle.textContent = 'Show All';
        let initialIndex = 0;

        stateColumns.forEach((col, index) => {
            col.style.display = index === initialIndex ? '' : 'none';
        });

        const paginationIndicator = modal.querySelector('.th-pagination-indicator');
        const prevBtn = modal.querySelector('.th-prev-btn');
        const nextBtn = modal.querySelector('.th-next-btn');

        if (paginationIndicator) {
            paginationIndicator.textContent = `${initialIndex + 1} / ${stateColumns.length}`;
        }
        if (prevBtn) prevBtn.disabled = initialIndex === 0;
        if (nextBtn) nextBtn.disabled = initialIndex >= stateColumns.length - 1;
        detachPaginationEvents(modal);
        attachPaginationEvents(modal, initialIndex);
    };

    const updateTransferPrices = (modal, newCurrency) => {
        const transferInfoSections = modal.querySelectorAll('.th-transfer-info');
        transferInfoSections.forEach(section => {
            const items = section.querySelectorAll('li');
            items.forEach(item => {
                const originalAmount = parseFloat(item.getAttribute('data-price-amount'));
                const originalCurrency = item.getAttribute('data-price-currency');
                if (originalAmount && originalCurrency) {
                    const priceObj = { amount: originalAmount, currency: originalCurrency };
                    const convertedPrice = convertPrice(priceObj, newCurrency);
                    const displayPrice = formatPrice(convertedPrice);
                    const text = item.innerHTML;
                    const pricePattern = /\([^)]*\)(?=[^(]*$)/;
                    item.innerHTML = text.replace(pricePattern, `(${displayPrice})`);
                }
            });
        });
    };

    const setUpCurrencyDropdowns = (modal) => {
        const icons = modal.querySelectorAll('.th-transfer-currency-icon');
        icons.forEach(icon => {
            const newIcon = icon.cloneNode(true);
            icon.parentNode.replaceChild(newIcon, icon);
        });

        modal.querySelectorAll('.th-transfer-currency-icon').forEach(icon => {
            icon.addEventListener('click', function(e) {
                e.stopPropagation();
                e.preventDefault();
                const dropdown = this.nextElementSibling;
                dropdown.classList.toggle('show');
                if (dropdown.classList.contains('show')) {
                    const rect = this.getBoundingClientRect();
                    dropdown.style.top = (rect.bottom - rect.top) + 5 + 'px';
                    dropdown.style.left = '0px';
                }
            });
        });

        modal.querySelectorAll('.th-transfer-currency-dropdown li').forEach(item => {
            const newItem = item.cloneNode(true);
            item.parentNode.replaceChild(newItem, item);
            newItem.addEventListener('click', function(e) {
                e.stopPropagation();
                const newCurrency = this.getAttribute('data-currency');
                preferredCurrency = newCurrency;
                GM_setValue('PREFERRED_CURRENCY', newCurrency);
                modal.querySelectorAll('.th-transfer-currency-dropdown li').forEach(li => {
                    li.classList.remove('selected');
                });
                modal.querySelectorAll(`.th-transfer-currency-dropdown li[data-currency="${newCurrency}"]`).forEach(li => {
                    li.classList.add('selected');
                });
                updateTransferPrices(modal, newCurrency);
                this.closest('.th-transfer-currency-dropdown').classList.remove('show');
            });
        });
    };

    const attachTabEvents = (modal) => {
        const tbs = modal.querySelectorAll('.th-tab-btn');
        const cs = modal.querySelectorAll('.th-tab-content');
        const paginationControls = modal.querySelector('.th-pagination-controls');
        const statesContent = modal.querySelector('.th-tab-content[data-content="states"]');

        if (!statesContent || !paginationControls) return;

        const updatePaginationVisibility = () => {
            const activeTab = modal.querySelector('.th-tab-btn.active')?.getAttribute('data-tab');
            paginationControls.style.display = (activeTab === 'states') ? '' : 'none';
        };

        setUpCurrencyDropdowns(modal);

        document.addEventListener('click', function(e) {
            if (!e.target.closest('.th-transfer-currency-dropdown') && !e.target.classList.contains('th-transfer-currency-icon')) {
                document.querySelectorAll('.th-transfer-currency-dropdown.show').forEach(dropdown => {
                    dropdown.classList.remove('show');
                });
            }
        });

        updatePaginationVisibility();

        tbs.forEach(btn => {
            btn.addEventListener('click', () => {
                tbs.forEach(x => x.classList.remove('active'));
                btn.classList.add('active');
                const t = btn.getAttribute('data-tab');
                cs.forEach(cc => {
                    cc.classList.toggle('active', cc.getAttribute('data-content') === t);
                });
                updatePaginationVisibility();
                if (t === 'states') {
                    initPaginationState(modal);
                }
            });
        });
    };

    const fetchScoutData = async (pid) => {
        try {
            const response = await fetch(`https://www.managerzone.com/ajax.php?p=players&sub=scout_report&pid=${pid}&sport=soccer`);
            if (!response.ok) throw new Error(`HTTP error ${response.status}`);
            const text = await response.text();
            const doc = new DOMParser().parseFromString(text, 'text/html');
            const defaultResult = { trainingSpeed: 0, hp: 0, lp: 0, firstHpSkill: '', secondHpSkill: '', firstLpSkill: '', secondLpSkill: '', hpPotentialIndices: [], lpPotentialIndices: [] };
            const paperContent = doc.querySelector('.paper-content');
            if (!paperContent) return defaultResult;

            const dlElement = paperContent.querySelector('dl');
            if (!dlElement) return defaultResult;

            const hpDd = dlElement.querySelector('dd i.fa-line-chart')?.closest('dd');
            const lpDd = dlElement.querySelector('dd i.fa-exclamation-triangle')?.closest('dd');
            const speedDd = dlElement.querySelector('dd i.fa-heartbeat')?.closest('dd');

            const getSkillsAndIndices = (dd) => {
                const skills = [];
                const indices = [];
                if (!dd) return { skills, indices };
                const listItems = dd.querySelectorAll('ul li');
                listItems.forEach((li, index) => {
                    const span = li.querySelector('.blurred span:last-child');
                    if (span) {
                        const skillName = span.textContent.trim();
                        skills.push(skillName);
                        if (li.querySelector('.stars i.fa-star.lit')) {
                            indices.push(index);
                        }
                    }
                });
                return { skills, indices };
            };

            const getStars = (dd) =>
            dd ? dd.querySelectorAll('.stars i.fa-star.lit').length : 0;

            const { skills: hpSkillsNative, indices: hpIndices } = getSkillsAndIndices(hpDd);
            const { skills: lpSkillsNative, indices: lpIndices } = getSkillsAndIndices(lpDd);

            const hpStars = getStars(hpDd);
            const lpStars = getStars(lpDd);
            const speedStars = getStars(speedDd);

            const firstHpNative = hpSkillsNative[0] || '';
            const secondHpNative = hpSkillsNative[1] || '';
            const firstLpNative = lpSkillsNative[0] || '';
            const secondLpNative = lpSkillsNative[1] || '';

            return {
                hp: hpStars,
                lp: lpStars,
                trainingSpeed: speedStars,
                firstHpSkill: getEnglishSkillName(firstHpNative),
                secondHpSkill: getEnglishSkillName(secondHpNative),
                firstLpSkill: getEnglishSkillName(firstLpNative),
                secondLpSkill: getEnglishSkillName(secondLpNative),
                hpPotentialIndices: hpIndices,
                lpPotentialIndices: lpIndices
            };
        } catch (error) {
            console.error("Failed to fetch or parse scout data:", error);
            return null;
        }
    };

    const fetchTransferData = async (pid, getSeasonFn) => {
        try {
            const response = await fetch(`https://www.managerzone.com/?p=players&pid=${pid}`);
            if (!response.ok) throw new Error(`HTTP error ${response.status}`);
            const text = await response.text();
            const doc = new DOMParser().parseFromString(text, 'text/html');
            const transferTable = doc.querySelector('table.hitlist');
            const transferDataHeader = transferTable?.querySelector('thead td strong');
            if (!transferTable || !transferDataHeader || !transferDataHeader.textContent.match(/Data|Date/i)) {
                return {};
            }

            const transfersBySeason = {};
            const rows = transferTable.querySelectorAll('tbody tr');

            rows.forEach(row => {
                const cells = row.querySelectorAll('td');
                if (cells.length < 5) return;
                const dateStrRaw = cells[0]?.textContent.trim();
                const actionOrFromTeamCell = cells[1];
                const toTeamCell = cells[3];
                const priceCell = cells[4];
                const dateMatch = dateStrRaw.match(/(\d{2})-(\d{2})-(\d{4})/);
                if (!dateMatch) return;
                const day = dateMatch[1];
                const month = dateMatch[2];
                const year = dateMatch[3];
                const transferDate = new Date(`${year}-${month}-${day}`);
                if (isNaN(transferDate)) return;
                const season = getSeasonFn(transferDate);
                const fromTeamLink = actionOrFromTeamCell.querySelector('a[href*="tid="]');
                const toTeamLink = toTeamCell.querySelector('a[href*="tid="]');
                let fromTeamName = 'Youth Academy';
                if (fromTeamLink) {
                    fromTeamName = fromTeamLink.textContent.trim();
                } else if (actionOrFromTeamCell.textContent.trim() !== '-') {
                    fromTeamName = actionOrFromTeamCell.textContent.trim();
                }
                const toTeamName = toTeamLink ? toTeamLink.textContent.trim() : toTeamCell.textContent.trim();
                const priceDiv = priceCell?.querySelector('div[title]');
                let price = priceDiv?.title || priceCell?.textContent.trim() || 'N/A';
                if (price === '-') price = 'N/A';

                if (!transfersBySeason[season]) {
                    transfersBySeason[season] = [];
                }

                transfersBySeason[season].push({
                    date: transferDate,
                    dateString: transferDate.toLocaleDateString(),
                    fromTeamName: fromTeamName,
                    toTeamName: toTeamName,
                    price: price
                });
            });

            Object.values(transfersBySeason).forEach(seasonTransfers => {
                seasonTransfers.sort((a, b) => a.date - b.date);
            });

            return transfersBySeason;
        } catch (error) {
            console.error("Failed to fetch or parse transfer data:", error);
            return {};
        }
    };

    function decodeHtmlEntities(text) {
        if (!text) return '';
        const textarea = document.createElement('textarea');
        textarea.innerHTML = text;
        return textarea.value;
    }

    async function fetchCurrentSkillsViaAjax(pid) {
        return new Promise((resolve, reject) => {
            const url = `https://www.managerzone.com/ajax.php?p=transfer&sub=transfer-search&sport=soccer&issearch=true&u=${pid}&nationality=all_nationalities&deadline=0&category=&valuea=&valueb=&bida=&bidb=&agea=15&ageb=45&birth_season_low=0&birth_season_high=100&tot_low=0&tot_high=120&s0a=0&s0b=10&s1a=0&s1b=10&s2a=0&s2b=10&s3a=0&s3b=10&s4a=0&s4b=10&s5a=0&s5b=10&s6a=0&s6b=10&s7a=0&s7b=10&s8a=0&s8b=10&s9a=0&s9b=10&s10a=0&s10b=10&s11a=0&s11b=10&s12a=0&s12b=10&o=0`;

            fetch(url, { credentials: 'include' })
                .then(response => {
                if (!response.ok) {
                    throw new Error(`HTTP Error: ${response.status}`);
                }
                return response.json();
            })
                .then(data => {
                if (data && data.players) {
                    try {
                        const decodedHtml = decodeHtmlEntities(data.players);
                        const parser = new DOMParser();
                        const ajaxDoc = parser.parseFromString(decodedHtml, 'text/html');
                        const skillsTable = ajaxDoc.querySelector('.player_skills');
                        if (skillsTable) {
                            const skills = {};
                            const skillRows = skillsTable.querySelectorAll('tbody > tr');
                            skillRows.forEach((row, index) => {
                                const skillId = (index + 1).toString();
                                const skillName = SKILL_MAP[skillId];
                                if (skillName) {
                                    const valueElem = row.querySelector('td.skillval > span');
                                    if (valueElem) {
                                        const value = parseInt(valueElem.textContent.trim().replace(/[()]/g, ''), 10);
                                        if (!isNaN(value)) {
                                            skills[skillName] = value;
                                        } else {
                                            skills[skillName] = 0;
                                        }
                                    } else {
                                        skills[skillName] = 0;
                                    }
                                }
                            });

                            if (Object.keys(skills).length === ORDERED_SKILLS.length) {
                                resolve(skills);
                            } else {
                                console.error("Skill extraction mismatch:", skills, ORDERED_SKILLS);
                                reject("Could not extract all expected skills from the AJAX response table.");
                            }
                        } else {
                            reject("Skills table not found in AJAX response.");
                        }
                    } catch (e) {
                        console.error("Error parsing AJAX response:", e);
                        reject("Error parsing AJAX response: " + e.message);
                    }
                } else {
                    reject("No player data found in AJAX response.");
                }
            })
                .catch(error => {
                console.error(`Error during fetch request for pid ${pid}:`, error);
                reject("Error during fetch request: " + error.message);
            });
        });
    }


    const fetchCombinedPlayerData = async (pid, node, getSeasonFn, csi) => {
        const cont = getPlayerContainerNode(node);
        if (!cont) return;
        const curSeason = csi.season;
        const nmEl = cont.querySelector('.player_name');
        const nm = nmEl ? nmEl.textContent.trim() : 'Unknown Player';
        const { modal, spinnerEl, spinnerInstance } = createModal('', true);
        const currentAge = parsePlayerAge(cont);

        try {
            let currentSkillsMap = {};
            const isSpecificPlayerPage = window.location.search.includes('p=players&pid=') && window.location.search.includes('&tid=');

            if (isSpecificPlayerPage) {
                try {
                    currentSkillsMap = await fetchCurrentSkillsViaAjax(pid);
                } catch (fetchError) {
                    console.error(`Failed to fetch current skills via AJAX for pid ${pid}:`, fetchError);
                    throw new Error(`Could not retrieve current skills. ${fetchError.message || fetchError}`);
                }
            } else {
                currentSkillsMap = gatherCurrentSkills(cont);
                if (Object.keys(currentSkillsMap).length === 0) {
                    console.warn(`Could not determine current skills from DOM for pid ${pid}. Player might be retired or skills hidden.`);
                    throw new Error("Could not read current skills from page.");
                }
            }

            if (!currentSkillsMap || Object.keys(currentSkillsMap).length === 0) {
                throw new Error("Failed to determine current skills for player.");
            }

            const [trainingResponse, scoutData, transferData] = await Promise.all([
                fetch(`https://www.managerzone.com/ajax.php?p=trainingGraph&sub=getJsonTrainingHistory&sport=soccer&player_id=${pid}`).then(res => res.ok ? res.text() : Promise.reject(`HTTP error ${res.status} for training data`)),
                fetchScoutData(pid),
                fetchTransferData(pid, getSeasonFn)
            ]);

            if (spinnerInstance) spinnerInstance.stop();
            spinnerEl.style.display = 'none';

            const series = parseSeriesData(trainingResponse);
            const processedTrainingData = processTrainingHistory(series, getSeasonFn);
            const transferSeasons = Object.keys(transferData).map(s => parseInt(s)).filter(s => !isNaN(s));
            let effectiveEarliestSeason = processedTrainingData.earliestSeason;

            if (transferSeasons.length > 0) {
                const earliestTransferSeason = Math.min(...transferSeasons);
                effectiveEarliestSeason = Math.max(1, Math.min(effectiveEarliestSeason, earliestTransferSeason));
            }

            if (effectiveEarliestSeason === 9999) {
                if (transferSeasons.length > 0) {
                    effectiveEarliestSeason = Math.max(1, Math.min(...transferSeasons));
                } else {
                    effectiveEarliestSeason = curSeason;
                }
            }
            processedTrainingData.earliestSeason = effectiveEarliestSeason;

            const evoHTML = generateEvolHTML(processedTrainingData, currentAge, curSeason);
            const stHTML = buildStatesLayout(processedTrainingData, currentSkillsMap, currentAge, curSeason, scoutData, transferData);
            const finalHTML = generateTabsHTML(nm, evoHTML, stHTML);
            modal.querySelector('.th-modal-content').innerHTML = finalHTML;

            setTimeout(() => {
                attachTabEvents(modal);
                initPaginationState(modal);
            }, 50);

        } catch (error) {
            console.error("Error fetching combined player data:", error);
            if (spinnerInstance) spinnerInstance.stop();
            if(spinnerEl) spinnerEl.style.display = 'none';
            const contentDiv = modal.querySelector('.th-modal-content');
            if (contentDiv) {
                contentDiv.innerHTML = `<div class="th-error-message"><p>Failed to process player data. (${error.message || 'Unknown error'})</p><p>(Are you a club member? Is the player still active? Check console for details.)</p></div>`;
            }
        }
    };

    const insertButtons = (getSeasonFn, csi) => {
        const containers = document.querySelectorAll('.playerContainer');
        const isPlayerProfilePage = window.location.search.includes('p=players&pid=');

        containers.forEach(cc => {
            const targetElements = cc.querySelectorAll('.floatRight[id^="player_id_"]');

            targetElements.forEach(ff => {
                const pidSpan = ff.querySelector('.player_id_span');
                if (!pidSpan) return;
                const pid = pidSpan.textContent.trim();
                if (!pid) return;

                const existingBtn = ff.querySelector('.th-btn');
                if (existingBtn) return;

                let shouldInsert = false;
                if (isPlayerProfilePage) {
                    const trainingGraphIcon = document.querySelector(
                        `span.player_icon_placeholder.training_graphs.soccer a[href*="p=training_graphs"][href*="pid=${pid}"]`
                    );
                    if (trainingGraphIcon) {
                        shouldInsert = true;
                    }
                } else {
                    if (hasVisibleSkills(cc)) {
                        shouldInsert = true;
                    }
                }

                if (shouldInsert) {
                    const b = document.createElement('button');
                    b.className = 'th-btn';
                    b.innerHTML = '<i class="fa fa-chart-line"></i>';
                    b.title = 'View Training History';
                    b.onclick = (e) => {
                        e.preventDefault();
                        fetchCombinedPlayerData(pid, ff, getSeasonFn, csi);
                    };
                    ff.appendChild(b);
                }
            });
        });
    };

    const initTeamId = () => {
        const stored = GM_getValue('TEAM_ID');
        if (stored) {
            myTeamId = stored;
            return;
        }
        const usernameEl = document.querySelector('#header-username');
        if (!usernameEl) return;
        const username = usernameEl.textContent.trim();
        if (!username) return;

        fetch(`https://www.managerzone.com/xml/manager_data.php?sport_id=1&username=${encodeURIComponent(username)}`)
            .then(response => {
            if (!response.ok) throw new Error(`HTTP error ${response.status}`);
            return response.text();
        })
            .then(text => {
            const parser = new DOMParser();
            const doc = parser.parseFromString(text, 'text/xml');
            const teamNodes = doc.querySelectorAll('Team[sport="soccer"]');
            if (!teamNodes || !teamNodes.length) return;
            const tid = teamNodes[0].getAttribute('teamId');
            if (tid) {
                GM_setValue('TEAM_ID', tid);
                myTeamId = tid;
            }
        })
            .catch((error) => {console.error("Failed to fetch team ID:", error)});
    };

    const run = () => {
        initTeamId();
        if (!canRunUserscript()) {
            console.log("MZ Training History: Not a Club Member");
            return;
        }
        const csi = getCurrentSeasonInfo();
        if (!csi) {
            console.error("MZ Training History: Could not determine current season info.");
            return;
        }
        const getSeasonFn = getSeasonCalculator(csi);
        insertButtons(getSeasonFn, csi);

        const obs = new MutationObserver((mutations) => {
            let playerContainerChanged = false;
            for (const mutation of mutations) {
                if (mutation.type === 'childList') {
                    if (mutation.target.matches && (mutation.target.matches('.playerContainer') || mutation.target.querySelector('.playerContainer'))) {
                        playerContainerChanged = true;
                        break;
                    }
                    for (const node of mutation.addedNodes) {
                        if (node.nodeType === 1 && (node.matches('.playerContainer') || node.querySelector('.playerContainer'))) {
                            playerContainerChanged = true;
                            break;
                        }
                    }
                    if (playerContainerChanged) break;
                }
            }
            if (playerContainerChanged) {
                insertButtons(getSeasonFn, csi);
            }
        });
        obs.observe(document.body, { childList: true, subtree: true });
    };
    run();
})();