MZ - Player Training History

Provides the player development/gains across previous MZ seasons

// ==UserScript==
// @name         MZ - Player Training History
// @namespace    douglaskampl
// @version      5.9
// @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/trainingHistoryNew.css
// @resource     skillTranslationsJS https://br18.org/16translations.js
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(() => {
    'use strict';

    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 {
        GM_addStyle(GM_getResourceText('trainingHistoryStyles'));
        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) {
            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 (_err) {
        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) {
            return null;
        }
    };

    const fetchTransferData = async (pid, getSeasonFn) => {
        const url = `https://www.managerzone.com/?p=players&pid=${pid}`;
        try {
            const response = await fetch(url);
            if (!response.ok) throw new Error(`HTTP error ${response.status}`);
            const text = await response.text();
            const doc = new DOMParser().parseFromString(text, 'text/html');

            let transferTable = null;
            const potentialTables = doc.querySelectorAll('table.hitlist');

            for (const table of potentialTables) {
                const tbody = table.querySelector('tbody');
                if (!tbody) continue;

                const firstRow = tbody.querySelector('tr');
                if (!firstRow) continue;

                const cells = firstRow.querySelectorAll('td');
                if (cells.length < 5) continue;

                const dateCellText = cells[0]?.textContent.trim();
                const priceCellText = cells[4]?.textContent.trim();
                const priceCellDiv = cells[4]?.querySelector('div[title]');

                const isDateLike = dateCellText && /(\d{2})-(\d{2})-(\d{4})|(\d{4})-(\d{2})-(\d{2})/.test(dateCellText);
                const isPriceLike = (priceCellText === '-') || (priceCellText && /\d/.test(priceCellText)) || priceCellDiv;

                if (isDateLike && isPriceLike) {
                    transferTable = table;
                    break;
                }
            }

            if (!transferTable) {
                return {};
            }

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

            rows.forEach(row => {
                const cells = row.querySelectorAll('td');
                if (cells.length < 5) return;

                const dateCell = cells[0];
                const actionOrFromTeamCell = cells[1];
                const toTeamCell = cells[3];
                const priceCell = cells[4];

                const dateStrRaw = dateCell?.textContent.trim();
                const dateMatch = dateStrRaw.match(/(\d{2})-(\d{2})-(\d{4})|(\d{4})-(\d{2})-(\d{2})/);
                if (!dateMatch) return;

                let day, month, year;
                if (dateMatch[1]) {
                    day = dateMatch[1]; month = dateMatch[2]; year = dateMatch[3];
                } else {
                    day = dateMatch[6]; month = dateMatch[5]; year = dateMatch[4];
                }
                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 {
                    const fromText = actionOrFromTeamCell.textContent.trim();
                    if (fromText && fromText !== '-') {
                        fromTeamName = fromText;
                    }
                }

                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) {
            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 {
                                reject("Could not extract all expected skills from the AJAX response table.");
                            }
                        } else {
                            reject("Skills table not found in AJAX response.");
                        }
                    } catch (e) {
                        reject("Error parsing AJAX response: " + e.message);
                    }
                } else {
                    reject("No player data found in AJAX response.");
                }
            })
                .catch(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.href.includes('/?p=players&pid=');

            if (isSpecificPlayerPage) {
                if (hasVisibleSkills(cont)) {
                    currentSkillsMap = gatherCurrentSkills(cont);
                } else {
                    try {
                        currentSkillsMap = await fetchCurrentSkillsViaAjax(pid);
                    } catch (fetchError) {
                        throw new Error(`Could not retrieve current skills via AJAX. ${fetchError.message || fetchError}`);
                    }
                }
            } else {
                currentSkillsMap = gatherCurrentSkills(cont);
            }

            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) {
            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></div>`;
            }
        }
    };

    const insertButtons = (getSeasonFn, csi) => {
        const containers = document.querySelectorAll('.playerContainer');
        const isPlayerProfilePage = window.location.href.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;
                const disabledGraphIcon = cc.querySelector('.training-graphs-icon.training-graphs-icon--disabled');

                if (!disabledGraphIcon) {
                    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((_err) => {});
    };

    const run = () => {
        initTeamId();
        if (!canRunUserscript()) {
            return;
        }
        const csi = getCurrentSeasonInfo();
        if (!csi) {
            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();
})();