MZ - NT Player Search

Searches for players who match specific requirements (NC/NCA only)

// ==UserScript==
// @name         MZ - NT Player Search
// @namespace    douglaskampl
// @version      2.99
// @description  Searches for players who match specific requirements (NC/NCA only)
// @author       Douglas Vieira
// @match        https://www.managerzone.com/?p=national_teams&type=senior
// @match        https://www.managerzone.com/?p=national_teams&type=u21
// @icon         https://yt3.googleusercontent.com/ytc/AIdro_mDHaJkwjCgyINFM7cdUV2dWPPnL9Q58vUsrhOmRqkatg=s160-c-k-c0x00ffffff-no-rj
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      mzlive.eu
// @connect      pub-02de1c06eac643f992bb26daeae5c7a0.r2.dev
// @connect      https://www.managerzone.com/
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    GM_addStyle(`@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');.nt-search-open-btn{display:inline-block;padding:4px 8px;color:white;font-weight:bold;text-decoration:none;font-size:12px;font-family:'Space Mono',monospace;background:linear-gradient(135deg, #ff6e40, #ff5252, #448aff);border-radius:4px;transition:all .2s ease-in-out;border:1px solid rgba(138,43,226,.1);text-shadow:1px 1px 2px rgba(0,0,0,.3);margin-left:10px;vertical-align:middle}.nt-search-open-btn:hover{background:linear-gradient(145deg,rgba(40,40,70,.9),rgba(50,50,90,.9));color:lightgray;text-decoration:none;box-shadow:inset 0 0 5px rgba(138,43,226,.2)}.nt-search-open-btn i{margin-left:5px;color:violet}.nt-search-container{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) scale(.95);background:linear-gradient(135deg,#0a0a0a 0%,#1a1a2e 100%);color:#f0f0f0;padding:2rem;border-radius:12px;box-shadow:0 8px 32px rgba(83,11,237,.3),0 4px 8px rgba(0,0,0,.2);z-index:9999;visibility:hidden;width:800px;max-width:99%;opacity:0;transition:all .3s cubic-bezier(0.4,0,0.2,1);border:1px solid rgba(138,43,226,.1)}.nt-search-container.visible{visibility:visible;opacity:1;transform:translate(-50%,-50%) scale(1)}.nt-search-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem;padding-bottom:1rem;border-bottom:1px solid rgba(138,43,226,.2)}.nt-search-header h2{font-family:'Space Mono',monospace;margin:0;color:violet;font-size:1.5rem;text-shadow:0 0 10px rgba(138,43,226,.5)}.nt-search-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:1rem;margin-bottom:1.5rem}.nt-search-field{display:flex;flex-direction:column;gap:.5rem}.nt-search-field label{color:#ff9966;font-size:.875rem;text-transform:uppercase;letter-spacing:1px}.nt-search-field select{padding:.75rem;border:1px solid rgba(138,43,226,.3);border-radius:8px;background:#1a1a2e;color:#f0f0f0;font-size:1rem;transition:all .2s;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23ff9966' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right .75rem center;background-size:1rem}.nt-search-field select:focus{outline:none;border-color:#ff9966;box-shadow:0 0 0 2px rgba(138,43,226,.2)}.nt-search-field select:disabled{opacity:0.5;cursor:not-allowed;background:#333}.nt-search-buttons{display:flex;justify-content:center;align-items:center;gap:1rem;margin-top:1rem}.nt-search-button{width:auto;max-width:300px;padding:0.5rem 1rem;background:#009b3a;color:#ffdf00;border:none;border-radius:8px;font-weight:500;font-size:0.9rem;cursor:pointer;transition:all .2s;text-transform:uppercase;letter-spacing:2px;box-shadow:0 4px 6px rgba(0,0,0,.1)}.nt-search-button:not(:disabled):hover{transform:translateY(-2px);box-shadow:0 6px 8px rgba(0,0,0,.2)}.nt-search-button:disabled{opacity:0.5;cursor:not-allowed;background:#666}.nt-search-log{margin-top:1rem;padding:1rem;background:rgba(26,26,46,.3);border-radius:8px;font-family:monospace;font-size:.875rem;max-height:150px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:#6366f1 #1a1a2e}.nt-search-log::-webkit-scrollbar{width:8px;height:8px}.nt-search-log::-webkit-scrollbar-track{background:#1a1a2e;border-radius:4px}.nt-search-log::-webkit-scrollbar-thumb{background:#6366f1;border-radius:4px}.nt-search-log::-webkit-scrollbar-thumb:hover{background:#4834d4}.nt-search-log-entry{margin-bottom:.5rem;padding:.5rem;background:rgba(26,26,46,.5);border-radius:4px;color:#00ffff;animation:slideIn 0.3s ease-out forwards;opacity:0;transform:translateX(-20px)}@keyframes slideIn{from{opacity:0;transform:translateX(-20px)}to{opacity:1;transform:translateX(0)}}.nt-search-guestbook-link{position:fixed;bottom:1rem;right:1rem;color:#ff9966;transition:all .2s}.nt-search-guestbook-link:hover{color:#6366f1;transform:scale(1.1)}.nt-search-country-select{width:200px}.nt-search-country-select select{width:100%}.nt-search-loading{display:flex;justify-content:center;align-items:center;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(10,10,20,.9);z-index:10000;visibility:hidden;opacity:0;transition:opacity .3s}.nt-search-loading.visible{visibility:visible;opacity:1}.nt-orbital-spinner{position:relative;width:60px;height:60px}.nt-orbiter{position:absolute;width:10px;height:10px;border-radius:50%;top:50%;left:50%;margin:-5px}.nt-orbiter:nth-child(1){background:violet;animation:orbit1 2s linear infinite}.nt-orbiter:nth-child(2){background:#ff9966;animation:orbit2 2s linear infinite 0.2s}.nt-orbiter:nth-child(3){background:#00ffff;animation:orbit3 2s linear infinite 0.4s}@keyframes orbit1{0%{transform:rotate(0deg) translateX(25px) rotate(0deg) scale(1)}50%{transform:rotate(180deg) translateX(25px) rotate(-180deg) scale(0.7)}100%{transform:rotate(360deg) translateX(25px) rotate(-360deg) scale(1)}}@keyframes orbit2{0%{transform:rotate(120deg) translateX(25px) rotate(-120deg) scale(1)}50%{transform:rotate(300deg) translateX(25px) rotate(-300deg) scale(0.7)}100%{transform:rotate(480deg) translateX(25px) rotate(-480deg) scale(1)}}@keyframes orbit3{0%{transform:rotate(240deg) translateX(25px) rotate(-240deg) scale(1)}50%{transform:rotate(420deg) translateX(25px) rotate(-420deg) scale(0.7)}100%{transform:rotate(600deg) translateX(25px) rotate(-600deg) scale(1)}}.nt-search-results-button{width:auto;max-width:300px;padding:0.5rem 1rem;background:#009b3a;color:#ffdf00;border:none;border-radius:8px;font-weight:500;font-size:0.9rem;cursor:pointer;transition:all .2s;text-transform:uppercase;letter-spacing:2px;box-shadow:0 4px 6px rgba(0,0,0,.1);display:none}.nt-search-results-button:hover{transform:translateY(-2px);box-shadow:0 6px 8px rgba(0,0,0,.2)}.nt-search-results-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:linear-gradient(135deg,#0a0a0a 0%,#1a1a2e 100%);color:#f0f0f0;padding:0;border-radius:12px;z-index:10001;width:90%;height:90vh;overflow:hidden;box-shadow:0 8px 32px rgba(83,11,237,.3);animation:modalSlideIn 0.3s ease-out forwards}@keyframes modalSlideIn{from{opacity:0;transform:translate(-50%,-48%)}to{opacity:1;transform:translate(-50%,-50%)}}.nt-search-results-header{position:sticky;top:0;display:flex;justify-content:space-between;align-items:center;padding:1.5rem;background:inherit;border-bottom:1px solid rgba(138,43,226,.2);z-index:1}.nt-search-results-title{font-family:'Space Mono',monospace;margin:0;font-size:1.5rem;color:#fff;text-shadow:0 0 10px rgba(138,43,226,.5)}.nt-search-results-close{background:none;border:none;color:#ff9966;font-size:1.5rem;cursor:pointer;transition:all 0.2s;padding:0.5rem}.nt-search-results-close:hover{color:#6366f1;transform:scale(1.1)}.nt-search-results-content{padding:1.5rem;height:calc(90vh - 5rem);overflow-y:auto;scrollbar-width:thin;scrollbar-color:#6366f1 #1a1a2e}.nt-search-results-content::-webkit-scrollbar{width:8px}.nt-search-results-content::-webkit-scrollbar-track{background:#1a1a2e}.nt-search-results-content::-webkit-scrollbar-thumb{background:#6366f1;border-radius:4px}.nt-search-results-content::-webkit-scrollbar-thumb:hover{background:#4834d4}.nt-search-players-container{display:flex;flex-wrap:wrap;gap:1.5rem;margin:1rem 0}.nt-search-player-card{background:rgba(26,26,46,.5);border-radius:8px;padding:1rem;transition:all 0.2s;border:1px solid rgba(138,43,226,.1);flex:1 1 calc(50% - 1rem);box-sizing:border-box;display:flex;flex-direction:column;min-width:350px}.nt-search-player-card:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(83,11,237,.2)}.nt-search-player-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem}.nt-search-player-info{flex:1}.nt-search-player-name{font-size:1.1rem;font-weight:bold;color:#fff;margin:0 0 0.5rem 0}.nt-search-player-name a{color:inherit;text-decoration:none}.nt-search-player-name a:hover{color:violet}.nt-search-player-details{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:.5rem;color:#ff9966;font-size:0.875rem}.nt-search-skills-list{margin-top:1rem;display:flex;flex-direction:column;gap:4px}.nt-search-skill-row{display:flex;align-items:center;background:transparent;padding:0;box-shadow:none;min-height:24px}.nt-search-skill-name{font-size:.8rem;color:#f0f0f0;flex-basis:80px;margin-right:8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.nt-search-skill-value{display:flex;align-items:center;gap:4px;color:#ff9966;flex-shrink:0}.nt-search-skill-value img{height:.9em;width:auto;vertical-align:middle}.nt-search-skill-value-text{font-size:.8rem;white-space:nowrap}.nt-search-player-total-balls{margin-top:0.75rem;font-weight:bold;color:#ffcc66;text-align:right;font-size:0.9rem}.nt-search-results-pagination{display:flex;justify-content:center;align-items:center;gap:1rem;padding:1rem 0;border-top:1px solid rgba(138,43,226,.1);border-bottom:1px solid rgba(138,43,226,.1);margin:0 -1.5rem 1rem -1.5rem}.nt-search-results-pagination.bottom{border-top:1px solid rgba(138,43,226,.1);border-bottom:none;margin-top:1rem;margin-bottom:0}.nt-search-results-pagination.top{border-bottom:1px solid rgba(138,43,226,.1);border-top:none;margin-bottom:1rem;margin-top:0}.nt-search-pagination-button{background:#1a1a2e;color:#f0f0f0;border:1px solid rgba(138,43,226,.3);border-radius:4px;padding:0.5rem 1rem;cursor:pointer;transition:all 0.2s}.nt-search-pagination-button:not(:disabled):hover{background:#2a2a4e;transform:translateY(-1px)}.nt-search-pagination-button:disabled{opacity:0.5;cursor:not-allowed}.nt-search-pagination-info{color:#ff9966;font-size:0.875rem}.nt-search-export-button{background:#1a1a2e;color:#f0f0f0;border:1px solid rgba(138,43,226,.3);border-radius:4px;padding:0.5rem 1rem;cursor:pointer;transition:all 0.2s;margin-left:1rem}.nt-search-export-button:hover{background:#2a2a4e;transform:translateY(-1px)}.nt-search-export-button:active{transform:translateY(1px)}.nt-search-header-controls{display:flex;align-items:center;gap:1rem}`);

    const MASSIVE_COUNTRIES = ['BR', 'CN', 'AR', 'SE', 'PL', 'TR'];
    const PLAYERS_PER_PAGE = 10;
    const ORDERED_SKILL_KEYS = [
        "speed", "stamina", "playIntelligence", "passing", "shooting", "heading",
        "keeping", "ballControl", "tackling", "aerialPassing", "setPlays", "experience"
    ];

    class Logger {
        constructor(container, flushInterval = 400) {
            this.container = container;
            this.flushInterval = flushInterval;
            this.queue = [];
            this.timeout = null;
            this.scheduled = false;
        }
        getTimestamp() {
            const now = new Date();
            return `[${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}]`;
        }
        log(message, type = 'info') {
            this.queue.push({ message: `${this.getTimestamp()} ${message}`, type });
            if (!this.scheduled) {
                this.scheduled = true;
                this.timeout = setTimeout(() => this.flush(), this.flushInterval);
            }
        }
        flush() {
            if (!this.queue.length || !this.container) {
                this.scheduled = false;
                return;
            }
            const fragment = document.createDocumentFragment();
            this.queue.forEach(({ message, type }) => {
                const entry = document.createElement('div');
                entry.className = `nt-search-log-entry ${type}`;
                entry.textContent = message;
                fragment.appendChild(entry);
            });
            this.container.appendChild(fragment);
            this.container.scrollTop = this.container.scrollHeight;
            this.queue = [];
            if (this.timeout) {
                clearTimeout(this.timeout);
                this.timeout = null;
            }
            this.scheduled = false;
        }
    }

    class RequestQueue {
        constructor(maxConcurrent = 5, delay = 100) {
            this.queue = [];
            this.maxConcurrent = maxConcurrent;
            this.delay = delay;
            this.running = 0;
            this.processed = 0;
        }
        add(request) {
            return new Promise((resolve, reject) => {
                const wrappedRequest = async () => {
                    try {
                        await new Promise(res => setTimeout(res, this.delay));
                        const result = await request();
                        this.processed++;
                        resolve(result);
                    } catch (error) {
                        reject(error);
                    } finally {
                        this.running--;
                        this.processNext();
                    }
                };
                this.queue.push(wrappedRequest);
                this.processNext();
            });
        }
        processNext() {
            while (this.running < this.maxConcurrent && this.queue.length > 0) {
                this.running++;
                const request = this.queue.shift();
                request();
            }
        }
        reset() {
            this.queue = [];
            this.running = 0;
            this.processed = 0;
        }
    }

    class ChunkProcessor {
        constructor(chunkSize = 25) {
            this.chunkSize = chunkSize;
        }
        async process(items, processFn, onChunkComplete) {
            const chunks = this.createChunks(items);
            let processed = 0;
            for (const chunk of chunks) {
                await Promise.all(chunk.map(processFn));
                processed += chunk.length;
                if (onChunkComplete) {
                    onChunkComplete(processed, items.length);
                }
                await new Promise(res => setTimeout(res, 50));
            }
        }
        createChunks(items) {
            const chunks = [];
            for (let i = 0; i < items.length; i += this.chunkSize) {
                chunks.push(items.slice(i, i + this.chunkSize));
            }
            return chunks;
        }
    }

    class NTPlayerParser {
        constructor(minRequirements) {
            this.minRequirements = minRequirements;
            this.logger = null;
        }
        parseSkills(html) {
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, 'text/html');
            const rows = doc.querySelectorAll('.player_skills tr');
            if (!rows.length) return null;
            const skills = {};
            let totalBalls = 0;
            const totalBallsElement = doc.querySelector('td[title] span.bold');
            if (totalBallsElement) {
                totalBalls = parseInt(totalBallsElement.textContent, 10) || 0;
            }
            let skillRows = Array.from(rows);
            if (skillRows.length > ORDERED_SKILL_KEYS.length) { skillRows = skillRows.slice(0, ORDERED_SKILL_KEYS.length); }
            skillRows.forEach((row, index) => {
                const valueCell = row.querySelector('.skillval');
                if (!valueCell) return;
                const rawValue = valueCell.textContent.replace(/[()]/g, "").trim();
                const value = parseInt(rawValue, 10);
                if (!isNaN(value)) {
                    skills[ORDERED_SKILL_KEYS[index]] = value;
                }
            });
            if (Object.keys(skills).length === 0) return null;
            ORDERED_SKILL_KEYS.forEach(key => {
                if (!(key in skills)) {
                    skills[key] = 0;
                }
            });
            if (!this.validateSkills(skills)) return null;
            return { skills, totalBalls };
        }
        validateSkills(skills) {
            return Object.entries(this.minRequirements)
                .filter(([key]) => key in skills && typeof skills[key] === 'number')
                .every(([key, minValue]) => skills[key] >= minValue);
        }
        async fetchAndParsePlayer(playerId, ntid, cid) {
            const url = `https://www.managerzone.com/ajax.php?p=nationalTeams&sub=search&ntid=${ntid}&cid=${cid}&type=national_team&pid=${playerId}&sport=soccer`;
            try {
                const response = await fetch(url);
                if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
                const html = await response.text();
                return this.parseSkills(html);
            } catch (error) {
                 if (this.logger) this.logger.log(`Error parsing player ${playerId}: ${error.message}`, 'error');
                return null;
            }
        }
    }

    class PlayerData {
        constructor(id, name, teamName, teamId, age, value, salary, totalBalls, skills) {
            this.id = id;
            this.name = name;
            this.teamName = teamName;
            this.teamId = teamId || null;
            this.age = age;
            this.value = value;
            this.salary = salary;
            this.totalBalls = totalBalls;
            this.skills = skills;
        }
        toExcelRow() {
            const row = {
                'ID': this.id,
                'Name': this.name,
                'Team': this.teamName,
                'Age': this.age,
                'Value': this.value,
                'Salary': this.salary,
                'Total Balls': this.totalBalls,
            };
            ORDERED_SKILL_KEYS.forEach(key => {
                row[NTPlayerSearcher.prototype.formatSkillName(key)] = this.skills[key] || 0;
            });
            return row;
        }
    }

    class NTPlayerSearcher {
        constructor() {
            this.requestQueue = new RequestQueue(5, 100);
            this.chunkProcessor = new ChunkProcessor(25);
            this.searchValues = {
                speed: 0,
                stamina: 0,
                playIntelligence: 0,
                passing: 0,
                shooting: 0,
                heading: 0,
                keeping: 0,
                ballControl: 0,
                tackling: 0,
                aerialPassing: 0,
                setPlays: 0,
                experience: 0,
                minAge: 16,
                maxAge: 40,
                totalBalls: 9,
                country: '',
                countryData: null
            };
            this.isSearching = false;
            this.teamIds = new Set();
            this.playerIds = new Map();
            this.processedLeagues = 0;
            this.totalLeagues = 0;
            this.validPlayers = new Map();
            this.loadingElement = null;
            this.logger = null;
            this.countries = [];
            this.userCountry = null;
            this.username = null;
            this.currentResultsPage = 1;
            this.resultsListeners = { prev: null, next: null, esc: null };
        }
        async fetchTopPlayers(country, page = 0, isU21 = false) {
            try {
                const baseUrl = `https://mzlive.eu/mzlive.php?action=list&type=top100&mode=players&country=${country}&cy=EUR`;
                const url = isU21 ? `${baseUrl}&age=u21&page=${page}` : `${baseUrl}&page=${page}`;
                const response = await this.requestQueue.add(() =>
                    new Promise((resolve, reject) => {
                        GM_xmlhttpRequest({
                            method: 'GET',
                            url,
                            onload: res => resolve(res),
                            onerror: err => reject(err),
                            ontimeout: () => reject(new Error(`Timeout fetching Top100 page ${page}`))
                        });
                    })
                );
                const data = JSON.parse(response.responseText);
                const players = (data.players || []).filter(player => {
                    return player.age >= this.searchValues.minAge && player.age <= this.searchValues.maxAge;
                });
                const playerEntries = players.map(player => [
                    player.id.toString(),
                    {
                        id: player.id.toString(),
                        name: player.name,
                        teamName: player.team_name,
                        teamId: player.team_id?.toString() || null,
                        age: player.age,
                        value: parseInt(player.value) || 0,
                        salary: 0
                    }
                ]);
                this.playerIds = new Map([...this.playerIds, ...playerEntries]);
                return players.map(player => player.id.toString());
            } catch (error) {
                if (this.logger) this.logger.log(`Error fetching Top100 players (page ${page}): ${error.message}`, 'error');
                return [];
            }
        }
        async fetchAllTop100Players(country) {
            const maxPages = MASSIVE_COUNTRIES.includes(country) ? 20 : 5;
            const isU21 = this.searchValues.maxAge <= 21;
            const pages = Array.from({ length: maxPages + 1 }, (_, i) => i);
            const chunkSize = 5;
            const results = [];
            if (this.logger) this.logger.log(`Fetching Top100 players...`);
            for (let i = 0; i < pages.length; i += chunkSize) {
                const chunk = pages.slice(i, i + chunkSize);
                const chunkResults = await Promise.all(
                    chunk.map(page => this.fetchTopPlayers(country, page, isU21))
                );
                results.push(...chunkResults);
                await new Promise(res => setTimeout(res, 100));
            }
            if (this.logger) this.logger.log(`Finished fetching Top100 players.`);
            return results.flat();
        }
        async fetchCountriesList() {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: 'https://pub-02de1c06eac643f992bb26daeae5c7a0.r2.dev/json/countries.json',
                    onload: res => resolve(JSON.parse(res.responseText)),
                    onerror: err => reject(err),
                    ontimeout: () => reject(new Error('Timeout fetching countries list'))
                });
            });
        }
        async fetchUserCountry() {
            const usernameElem = document.querySelector('#header-username');
            if (!usernameElem) return { userCountry: null, username: null };
            const username = usernameElem.textContent.trim();
            try {
                const response = await fetch(`https://www.managerzone.com/xml/manager_data.php?sport_id=1&username=${username}`);
                 if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
                const text = await response.text();
                const parser = new DOMParser();
                const xmlDoc = parser.parseFromString(text, "text/xml");
                const parserError = xmlDoc.querySelector('parsererror');
                 if (parserError) {
                     console.error('XML parsing error:', parserError.textContent);
                     return { userCountry: null, username: username };
                 }
                 const countryCode = xmlDoc.querySelector('UserData')?.getAttribute('countryShortname') || null;
                return { userCountry: countryCode, username: username };
            } catch (error) {
                 console.error("Error fetching user country:", error);
                 if (this.logger) this.logger.log(`Error fetching user country: ${error.message}`, 'error');
                 return { userCountry: null, username: username };
            }
        }
        async checkUserRole(ntid, cid, username) {
            if (!ntid || !cid || !username) {
                if (this.logger) this.logger.log("Missing ntid, cid, or username for role check.", "warn");
                return false;
            }
            const url = `https://www.managerzone.com/ajax.php?p=nationalTeams&sub=team&ntid=${ntid}&cid=${cid}&type=national_team&sport=soccer`;
            try {
                const response = await fetch(url);
                if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
                const html = await response.text();
                const parser = new DOMParser();
                const doc = parser.parseFromString(html, 'text/html');
                const profileLinks = doc.querySelectorAll('table.padding a[href*="/?p=profile&uid="]');
                for (const link of profileLinks) {
                    if (link.textContent.trim() === username) {
                        if (this.logger) this.logger.log(`User ${username} confirmed as NC/NCA.`, 'info');
                        return true;
                    }
                }
                if (this.logger) this.logger.log(`User ${username} is not NC or NCA for this country.`, 'info');
                return false;
            } catch (error) {
                console.error(`Error checking user role: ${error.message}`);
                if (this.logger) this.logger.log(`Error checking user role: ${error.message}`, 'error');
                return false;
            }
        }
        async init() {
            this.createLoadingElement();
            this.showLoading();
            try {
                const [countries, { userCountry, username }] = await Promise.all([
                    this.fetchCountriesList(),
                    this.fetchUserCountry()
                ]);
                this.countries = countries || [];
                this.userCountry = userCountry;
                this.username = username;
                let isAuthorized = false;
                let userCountryData = null;
                if (this.userCountry && this.username && this.countries.length > 0) {
                    userCountryData = this.countries.find(c => c.code === this.userCountry);
                    if (userCountryData) {
                        this.searchValues.country = this.userCountry;
                        this.searchValues.countryData = { ntid: userCountryData.ntid, cid: userCountryData.cid };
                    }
                 }
                 const tempLogContainer = document.createElement('div');
                 this.logger = new Logger(tempLogContainer);
                 if (userCountryData && this.username) {
                    isAuthorized = await this.checkUserRole(userCountryData.ntid, userCountryData.cid, this.username);
                 } else {
                     if (this.logger) this.logger.log("Could not find user country data or username.", "warn");
                 }

                if (isAuthorized) {
                    const appended = await this.appendSearchTab();
                    if (!appended) {
                        throw new Error("Failed to append search tab elements.");
                    }
                    const logContainer = document.querySelector('.nt-search-log');
                    if (logContainer) {
                         this.logger.container = logContainer;
                        if (!this.userCountry) {
                             this.logger.log("Could not determine user country data.", "warn");
                        }
                    } else {
                        console.error("Log container not found after appending tab.");
                        this.logger = { log: console.log, flush: () => {} };
                    }
                    this.setUpEvents();
                } else {
                    console.log("User is not authorized (NC/NCA) or required data missing. NT Search UI not added.");
                    if (this.logger) this.logger.log("User not authorized (NC/NCA) or essential data missing. Search tool disabled.", "warn");
                }
            } catch (error) {
                 console.error("Initialization failed:", error);
                 if (this.logger) {
                    this.logger.log(`Initialization failed: ${error.message}`, 'error');
                 } else {
                    alert(`Initialization failed: ${error.message}`);
                 }
            }
            finally {
                if (this.logger) this.logger.flush();
                this.hideLoading();
            }
        }
        createLoadingElement() {
            if (this.loadingElement) return;
            this.loadingElement = document.createElement('div');
            this.loadingElement.className = 'nt-search-loading';
            const spinnerContainer = document.createElement('div');
            spinnerContainer.className = 'nt-orbital-spinner';
            spinnerContainer.innerHTML = `
                <div class="nt-orbiter"></div>
                <div class="nt-orbiter"></div>
                <div class="nt-orbiter"></div>
            `;
            this.loadingElement.appendChild(spinnerContainer);
            document.body.appendChild(this.loadingElement);
        }
        showLoading() {
            if (this.loadingElement) {
                this.loadingElement.classList.add('visible');
            }
        }
        hideLoading() {
            if (this.loadingElement) {
                this.loadingElement.classList.remove('visible');
            }
        }
        async getLeagueIds(countryCode) {
            try {
                const response = await this.requestQueue.add(() =>
                    new Promise((resolve, reject) => {
                        GM_xmlhttpRequest({
                            method: 'GET',
                            url: `https://mzlive.eu/mzlive.php?action=list&type=leagues&country=${countryCode}`,
                            onload: res => resolve(res),
                            onerror: err => reject(err),
                            ontimeout: () => reject(new Error('Timeout fetching leagues'))
                        });
                    })
                );
                const leagues = JSON.parse(response.responseText);
                const maxDivision = MASSIVE_COUNTRIES.includes(countryCode) ? 6 : 3;
                return leagues.filter(league => {
                    const name = league.name.toLowerCase();
                    if (name.startsWith('div')) {
                        const divLevel = parseInt(name.split('.')[0].replace('div', ''));
                         if(isNaN(divLevel)) return true;
                        return divLevel <= maxDivision;
                    }
                    return true;
                }).map(league => league.id);
            } catch (error) {
                if (this.logger) this.logger.log(`Error fetching leagues: ${error.message}`, 'error');
                 return [];
            }
        }
        async getTeamIds(leagueId) {
            try {
                const response = await this.requestQueue.add(() =>
                    fetch(`https://www.managerzone.com/xml/team_league.php?sport_id=1&league_id=${leagueId}`)
                );
                 if (!response.ok) throw new Error(`HTTP error! status: ${response.status} for league ${leagueId}`);
                const text = await response.text();
                const parser = new DOMParser();
                const xmlDoc = parser.parseFromString(text, "text/xml");
                const parserError = xmlDoc.querySelector('parsererror');
                 if (parserError) {
                     console.error(`XML parsing error for league ${leagueId}:`, parserError.textContent);
                     throw new Error(`XML parsing error for league ${leagueId}`);
                 }
                const teams = xmlDoc.getElementsByTagName('Team');
                return Array.from(teams).map(team => team.getAttribute('teamId'));
            } catch (error) {
                if (this.logger) this.logger.log(`Error fetching teams for league ${leagueId}: ${error.message}`, 'error');
                return [];
            }
        }
        async processLeagueBatch(leagueIds) {
            if (!leagueIds || leagueIds.length === 0) {
                 if (this.logger) this.logger.log("No league IDs to process.", "warn");
                 return;
            }
             if (this.logger) this.logger.log(`Processing ${leagueIds.length} leagues...`);
            await this.chunkProcessor.process(
                leagueIds,
                async (leagueId) => {
                    try {
                        const teamIds = await this.getTeamIds(leagueId);
                         if (teamIds && teamIds.length > 0) {
                             teamIds.forEach(id => this.teamIds.add(id));
                         }
                        this.processedLeagues++;
                    } catch (error) {
                         if (this.logger) this.logger.log(`Failed to process league ${leagueId}: ${error}`, 'error');
                    }
                }
            );
             if (this.logger) this.logger.log(`Finished processing leagues. Found ${this.teamIds.size} unique teams.`);
        }
        async fetchPlayerList(teamId) {
            try {
                const response = await this.requestQueue.add(() =>
                    fetch(`https://www.managerzone.com/xml/team_playerlist.php?sport_id=1&team_id=${teamId}`)
                );
                 if (!response.ok) throw new Error(`HTTP error! status: ${response.status} for team ${teamId}`);
                const text = await response.text();
                const parser = new DOMParser();
                const xmlDoc = parser.parseFromString(text, "text/xml");
                const parserError = xmlDoc.querySelector('parsererror');
                 if (parserError) {
                    console.error(`XML parsing error for team ${teamId}:`, parserError.textContent);
                    throw new Error(`XML parsing error for team ${teamId}`);
                 }
                const teamPlayers = xmlDoc.querySelector('TeamPlayers');
                 if (!teamPlayers) {
                     if (this.logger) this.logger.log(`No TeamPlayers data found for team ${teamId}`, 'warn');
                     return;
                 }
                const teamName = teamPlayers.getAttribute('teamName') || `Team ${teamId}`;
                const actualTeamId = teamPlayers.getAttribute('teamId') || teamId;
                const players = xmlDoc.getElementsByTagName('Player');
                const targetCountry = this.searchValues.country.toLowerCase();
                Array.from(players).forEach(player => {
                    const age = parseInt(player.getAttribute('age'));
                    const countryCodeAttr = player.getAttribute('countryShortname');
                     if(!countryCodeAttr) return;
                    const countryCode = countryCodeAttr.toLowerCase();
                    if (age >= this.searchValues.minAge && age <= this.searchValues.maxAge && countryCode === targetCountry) {
                         const playerId = player.getAttribute('id');
                         const playerName = player.getAttribute('name');
                         const value = parseInt(player.getAttribute('value')) || 0;
                         const salary = parseInt(player.getAttribute('salary')) || 0;
                         if (playerId && playerName) {
                            this.playerIds.set(playerId, {
                                id: playerId,
                                name: playerName,
                                teamName: teamName,
                                teamId: actualTeamId,
                                age: age,
                                value: value,
                                salary: salary
                            });
                        }
                    }
                });
            } catch (error) {
                if (this.logger) this.logger.log(`Error fetching players for team ${teamId}: ${error.message}`, 'error');
            }
        }
        async processTeamBatch(teamIds) {
             if (!teamIds || teamIds.length === 0) {
                 if (this.logger) this.logger.log("No team IDs to process.", "warn");
                 return;
             }
             const totalTeams = teamIds.length;
             let processedTeams = 0;
             if (this.logger) this.logger.log(`Processing ${totalTeams} teams...`);
             await this.chunkProcessor.process(
                 teamIds,
                 async (teamId) => {
                     await this.fetchPlayerList(teamId);
                     processedTeams++;
                     if (processedTeams % 100 === 0 || processedTeams === totalTeams) {
                        if (this.logger) this.logger.log(`Team processing: ${processedTeams}/${totalTeams}`);
                     }
                 }
             );
             if (this.logger) this.logger.log(`Finished processing teams. Found ${this.playerIds.size} potential players.`);
        }
        async searchForPlayers() {
            if (!this.searchValues.country || !this.searchValues.countryData) {
                if (this.logger) this.logger.log('Country not selected or country data missing.', 'error');
                alert('Please ensure a country is selected.');
                return;
            }
            this.teamIds = new Set();
            this.playerIds = new Map();
            this.processedLeagues = 0;
            this.totalLeagues = 0;
            this.validPlayers = new Map();
            this.requestQueue.reset();
            this.currentResultsPage = 1;
            const countryCode = this.searchValues.country;
            if (this.logger) this.logger.log(`Starting search for country: ${countryCode}`);
            try {
                if (this.searchValues.maxAge > 18) {
                    await this.fetchAllTop100Players(countryCode);
                    if (this.logger) this.logger.log(`Found ${this.playerIds.size} players from Top100.`);
                }
                const leagueIds = await this.getLeagueIds(countryCode);
                this.totalLeagues = leagueIds.length;
                if(this.totalLeagues === 0 && this.playerIds.size === 0){
                   if (this.logger) this.logger.log(`No leagues found and no top players matched. Stopping search.`, 'warn');
                   return;
                }
                await this.processLeagueBatch(leagueIds);
                await this.processTeamBatch(Array.from(this.teamIds));
                const { ntid, cid } = this.searchValues.countryData;
                const ntPlayerParser = new NTPlayerParser(this.searchValues);
                ntPlayerParser.logger = this.logger;
                const playerEntries = Array.from(this.playerIds.entries());
                 if(playerEntries.length === 0) {
                    if (this.logger) this.logger.log('No potential players found after gathering IDs.', 'warn');
                    return;
                 }
                if (this.logger) this.logger.log(`Processing skills for ${playerEntries.length} players...`);
                let processedCount = 0;
                let validCount = 0;
                 const totalPlayersToParse = playerEntries.length;
                 const yieldFrequency = 25;
                await this.chunkProcessor.process(
                    playerEntries,
                    async ([playerId, playerData]) => {
                        try {
                            const parsedData = await ntPlayerParser.fetchAndParsePlayer(playerId, ntid, cid);
                            if (parsedData && parsedData.totalBalls >= this.searchValues.totalBalls) {
                                this.validPlayers.set(playerId, new PlayerData(
                                    playerId,
                                    playerData.name,
                                    playerData.teamName,
                                    playerData.teamId,
                                    playerData.age,
                                    playerData.value,
                                    playerData.salary,
                                    parsedData.totalBalls,
                                    parsedData.skills
                                ));
                                validCount++;
                            }
                        } catch (parseError) {
                             if (this.logger) this.logger.log(`Error processing player ${playerId} (${playerData.name}): ${parseError.message}`, 'error');
                        } finally {
                             processedCount++;
                             if (processedCount % 100 === 0 || processedCount === totalPlayersToParse) {
                                 if (this.logger) this.logger.log(`Skill check progress: ${processedCount}/${totalPlayersToParse}`);
                             }
                             if (processedCount % yieldFrequency === 0) {
                                 await new Promise(res => setTimeout(res, 0));
                             }
                        }
                    }
                );
                if (this.logger) this.logger.log('Finishing skill processing...');
                await new Promise(resolve => setTimeout(resolve, 200));
                const finalCount = this.validPlayers.size;
                if (this.logger) this.logger.log(`Search complete: found ${finalCount} players matching all criteria.`);
                const resultsButton = document.querySelector('.nt-search-results-button');
                 if(resultsButton) resultsButton.style.display = finalCount > 0 ? "inline-block" : "none";
                return Array.from(this.validPlayers.keys());
            } catch (error) {
                 if (this.logger) this.logger.log(`Critical error during search: ${error.message}`, 'error');
                 console.error('Search failed critically:', error);
                 alert(`An error occurred during the search: ${error.message}`);
            } finally {
                 if (this.logger) this.logger.flush();
            }
        }
        async performSearch() {
            if (this.isSearching) {
                if(this.logger) this.logger.log("Search already in progress.", "warn");
                return;
            }
             if (!this.searchValues.country || !this.searchValues.countryData) {
                 alert("Please select a country before searching.");
                 return;
             }
            this.isSearching = true;
            const internalSearchButton = document.querySelector('.nt-search-container .nt-search-button');
            if(internalSearchButton) internalSearchButton.disabled = true;
            const logContainer = document.querySelector('.nt-search-log');
            const resultsButton = document.querySelector('.nt-search-results-button');
            this.showLoading();
            if(logContainer) logContainer.innerHTML = '';
            if(resultsButton) resultsButton.style.display = 'none';
             if (!this.logger || !this.logger.container) {
                 console.error("Logger not initialized before search start.");
                 const logCont = document.querySelector('.nt-search-log');
                 if(logCont) this.logger = new Logger(logCont);
                 else this.logger = { log: console.log, flush: () => {} };
             }
            try {
                await this.searchForPlayers();
            } catch (error) {
                if (this.logger) this.logger.log(`Unhandled error during search execution: ${error.message}`, 'error');
                console.error('Search execution failed:', error);
                 alert(`An unexpected error occurred: ${error.message}`);
            } finally {
                this.isSearching = false;
                 if(internalSearchButton) internalSearchButton.disabled = false;
                this.hideLoading();
                 if(this.logger) this.logger.flush();
            }
        }
        getFiltersAppliedText() {
            const filters = [];
             const countryName = this.countries.find(c => c.code === this.searchValues.country)?.name || this.searchValues.country;
             if (countryName) {
                filters.push(`Country: ${countryName}`);
            }
            filters.push(`Age: ${this.searchValues.minAge} - ${this.searchValues.maxAge}`);
            filters.push(`Min Total Balls: ${this.searchValues.totalBalls}`);
            ORDERED_SKILL_KEYS.forEach(skill => {
                if (this.searchValues[skill] > 0) {
                    filters.push(`Min ${this.formatSkillName(skill)}: ${this.searchValues[skill]}`);
                }
            });
            return filters.join('; ');
        }
        createPaginationControls(page, totalPages) {
            const container = document.createElement('div');
            container.className = 'nt-search-results-pagination';
            if (totalPages > 1) {
                const prevBtn = document.createElement('button');
                prevBtn.className = 'nt-search-pagination-button';
                prevBtn.textContent = 'Previous';
                prevBtn.disabled = page === 1;
                prevBtn.dataset.action = "prev";
                const pageInfo = document.createElement('span');
                pageInfo.className = 'nt-search-pagination-info';
                pageInfo.textContent = `Page ${page} of ${totalPages}`;
                const nextBtn = document.createElement('button');
                nextBtn.className = 'nt-search-pagination-button';
                nextBtn.textContent = 'Next';
                nextBtn.disabled = page === totalPages;
                nextBtn.dataset.action = "next";
                container.appendChild(prevBtn);
                container.appendChild(pageInfo);
                container.appendChild(nextBtn);
            }
            return container;
        }
        renderResultsPage(page) {
            const playersContainer = document.querySelector('.nt-search-players-container');
            const paginationTopContainer = document.querySelector('.nt-search-results-pagination.top');
            const paginationBottomContainer = document.querySelector('.nt-search-results-pagination.bottom');
            const modalContent = document.querySelector('.nt-search-results-content');
            if (!playersContainer || !paginationTopContainer || !paginationBottomContainer || !modalContent) return;
            this.currentResultsPage = page;
            playersContainer.textContent = '';
            paginationTopContainer.textContent = '';
            paginationBottomContainer.textContent = '';
            const playersArray = Array.from(this.validPlayers.values())
                .sort((a, b) => b.totalBalls - a.totalBalls);
            const totalPages = Math.ceil(playersArray.length / PLAYERS_PER_PAGE);
            const startIndex = (page - 1) * PLAYERS_PER_PAGE;
            const pagePlayers = playersArray.slice(startIndex, startIndex + PLAYERS_PER_PAGE);
            this.removePaginationListeners();
            this.resultsListeners.prev = () => {
                if (this.currentResultsPage > 1) {
                    this.renderResultsPage(this.currentResultsPage - 1);
                    if(modalContent) modalContent.scrollTop = 0;
                }
            };
            this.resultsListeners.next = () => {
                if (this.currentResultsPage < totalPages) {
                    this.renderResultsPage(this.currentResultsPage + 1);
                    if(modalContent) modalContent.scrollTop = 0;
                }
            };
            const topControls = this.createPaginationControls(page, totalPages);
            const bottomControls = this.createPaginationControls(page, totalPages);
            paginationTopContainer.appendChild(topControls);
            paginationBottomContainer.appendChild(bottomControls);
            this.addPaginationListeners(paginationTopContainer);
            this.addPaginationListeners(paginationBottomContainer);
             const fragment = document.createDocumentFragment();
            pagePlayers.forEach(player => {
                let skillsHTML = '';
                ORDERED_SKILL_KEYS.forEach((skillKey) => {
                    const value = player.skills[skillKey] || 0;
                    const skillName = this.formatSkillName(skillKey);
                    skillsHTML += `
                        <div class="nt-search-skill-row" title="${skillName}: ${value}">
                            <span class="nt-search-skill-name">${skillName}</span>
                            <div class="nt-search-skill-value">
                                <img src="/img/soccer/wlevel_${value}.gif" alt="${value}">
                                <span class="nt-search-skill-value-text">(${value})</span>
                            </div>
                        </div>
                    `;
                });
                const skillsContainerHTML = `<div class="nt-search-skills-list">${skillsHTML}</div>`;
                const playerCard = document.createElement('div');
                playerCard.className = 'nt-search-player-card';
                playerCard.innerHTML = `
                    <div class="nt-search-player-header">
                        <div class="nt-search-player-info">
                            <h3 class="nt-search-player-name">
                                <a href="https://www.managerzone.com/?p=players&pid=${player.id}" target="_blank" title="View player profile (ID: ${player.id})">
                                    ${player.name}
                                </a>
                            </h3>
                            <div class="nt-search-player-details">
                                <span>Team: ${player.teamId ? `<a href="https://www.managerzone.com/?p=team&tid=${player.teamId}" target="_blank" title="View team profile">${player.teamName}</a>` : player.teamName}</span>
                                <span>Age: ${player.age}</span>
                                <span>Value: ${new Intl.NumberFormat('en-US').format(player.value)} USD</span>
                            </div>
                        </div>
                    </div>
                    ${skillsContainerHTML}
                    <div class="nt-search-player-total-balls">Total Balls: ${player.totalBalls}</div>
                `;
                fragment.appendChild(playerCard);
            });
            playersContainer.appendChild(fragment);
        }
        addPaginationListeners(container) {
            const prevBtn = container.querySelector('[data-action="prev"]');
            const nextBtn = container.querySelector('[data-action="next"]');
            if (prevBtn && this.resultsListeners.prev) prevBtn.addEventListener('click', this.resultsListeners.prev);
            if (nextBtn && this.resultsListeners.next) nextBtn.addEventListener('click', this.resultsListeners.next);
        }
        removePaginationListeners() {
            const containers = document.querySelectorAll('.nt-search-results-pagination');
            containers.forEach(container => {
                const prevBtn = container.querySelector('[data-action="prev"]');
                const nextBtn = container.querySelector('[data-action="next"]');
                if (prevBtn && this.resultsListeners.prev) prevBtn.removeEventListener('click', this.resultsListeners.prev);
                if (nextBtn && this.resultsListeners.next) nextBtn.removeEventListener('click', this.resultsListeners.next);
            });
        }
        showResults() {
            if (this.validPlayers.size === 0) {
                alert("No valid players found matching the criteria.");
                return;
            }
            const existingModal = document.querySelector('.nt-search-results-modal');
             if (existingModal) existingModal.remove();
            this.removePaginationListeners();
            if (this.resultsListeners.esc) {
                document.removeEventListener('keydown', this.resultsListeners.esc);
                this.resultsListeners.esc = null;
            }
            const modal = document.createElement('div');
            modal.className = 'nt-search-results-modal';
            const modalHeader = document.createElement('div');
            modalHeader.className = 'nt-search-results-header';
            const headerControls = document.createElement('div');
            headerControls.className = 'nt-search-header-controls';
            const closeButton = document.createElement('button');
            closeButton.className = 'nt-search-results-close';
            closeButton.innerHTML = '×';
            closeButton.title = 'Close Results (Esc)';
            const exportButton = document.createElement('button');
            exportButton.className = 'nt-search-export-button';
            exportButton.textContent = 'Export';
            exportButton.title = 'Export results to Excel (.xlsx)';
            exportButton.onclick = () => this.exportToExcel();
            headerControls.appendChild(exportButton);
            headerControls.appendChild(closeButton);
            modalHeader.innerHTML = `
                <div>
                    <h2 class="nt-search-results-title">Search Results (${this.validPlayers.size})</h2>
                    <div class="nt-search-results-filters" style="font-size: 0.8rem; color: #bbb; margin-top: 0.5rem; max-width: 700px; line-height: 1.4;">
                        <strong style="color: #ff9966;">Filters:</strong> ${this.getFiltersAppliedText()}
                    </div>
                </div>`;
            modalHeader.appendChild(headerControls);
            const modalContent = document.createElement('div');
            modalContent.className = 'nt-search-results-content';
            const paginationTopContainer = document.createElement('div');
            paginationTopContainer.className = 'nt-search-results-pagination top';
            const playersContainer = document.createElement('div');
            playersContainer.className = 'nt-search-players-container';
            const paginationBottomContainer = document.createElement('div');
            paginationBottomContainer.className = 'nt-search-results-pagination bottom';
            modalContent.appendChild(paginationTopContainer);
            modalContent.appendChild(playersContainer);
            modalContent.appendChild(paginationBottomContainer);
            modal.appendChild(modalHeader);
            modal.appendChild(modalContent);
            document.body.appendChild(modal);
            this.renderResultsPage(this.currentResultsPage);
            const closeModal = () => {
                 this.removePaginationListeners();
                 if (this.resultsListeners.esc) {
                     document.removeEventListener('keydown', this.resultsListeners.esc);
                     this.resultsListeners.esc = null;
                 }
                 modal.remove();
             };
            closeButton.addEventListener('click', closeModal);
            this.resultsListeners.esc = (e) => {
                if (e.key === 'Escape') {
                    closeModal();
                }
            };
            document.addEventListener('keydown', this.resultsListeners.esc);
        }
        formatSkillName(skill) {
            const names = {
                speed: 'Speed',
                stamina: 'Stamina',
                playIntelligence: 'Play Int',
                passing: 'Passing',
                shooting: 'Shooting',
                heading: 'Heading',
                keeping: 'Keeping',
                ballControl: 'Ball Ctrl',
                tackling: 'Tackling',
                aerialPassing: 'Aerial Pass',
                setPlays: 'Set Plays',
                experience: 'Experience'
            };
            return names[skill] || skill.charAt(0).toUpperCase() + skill.slice(1);
        }
        exportToExcel() {
            if (this.validPlayers.size === 0) {
                 alert("No players to export.");
                 return;
             }
            try {
                 const dataToExport = Array.from(this.validPlayers.values())
                    .sort((a,b) => b.totalBalls - a.totalBalls)
                    .map(player => player.toExcelRow());
                 if(dataToExport.length === 0) {
                    alert("No data formatted for export.");
                    return;
                 }
                 const worksheet = XLSX.utils.json_to_sheet(dataToExport);
                 const workbook = XLSX.utils.book_new();
                 XLSX.utils.book_append_sheet(workbook, worksheet, "Players");
                 const date = new Date().toISOString().slice(0, 10);
                 const countryCode = this.searchValues.country || 'export';
                 const filename = `MZ_NT_Search_${countryCode}_${date}.xlsx`;
                 XLSX.writeFile(workbook, filename);
                 if (this.logger) this.logger.log(`Exported ${dataToExport.length} players to ${filename}`);
             } catch (error) {
                 console.error("Excel export failed:", error);
                 alert(`Excel export failed: ${error.message}`);
                 if (this.logger) this.logger.log(`Excel export failed: ${error.message}`, 'error');
             }
        }
        async appendSearchTab() {
            const targetSelect = document.querySelector('#menuForm select#type');
            if (!targetSelect) {
                console.error('Target select element (#menuForm select#type) not found.');
                return false;
            }
            const existingButton = document.querySelector('.nt-search-open-btn');
             if (existingButton) existingButton.remove();
             const existingContainer = document.querySelector('.nt-search-container');
             if (existingContainer) existingContainer.remove();
            const openButton = document.createElement('a');
            openButton.href = '#';
            openButton.className = 'nt-search-open-btn';
            openButton.innerHTML = 'NTPlayerSearch <i class="fa fa-search" style="margin-left: 5px;"></i>';
            openButton.title = 'Open National Team Player Search';
            targetSelect.insertAdjacentElement('afterend', openButton);
            const searchContainer = document.createElement('div');
            searchContainer.className = 'nt-search-container';
            const goText = ((lang) => ({
                 pt: 'Buscar', es: 'Buscar', fr: 'Chercher', de: 'Suchen', it: 'Cerca',
                 pl: 'Szukaj', tr: 'Ara', ro: 'Caută', sv: 'Sök', nl: 'Zoeken'
             }[lang.slice(0, 2)] || 'Search'))(navigator.language);
            const skillFields = ORDERED_SKILL_KEYS.map(key => [key, this.formatSkillName(key)]);
            const skillsHTML = skillFields.map(([field, label]) => `
                <div class="nt-search-field">
                    <label title="Minimum ${label}">${label}</label>
                    <select name="${field}" title="Select minimum ${label}">
                        ${this.generateOptions(10, 0, field)}
                    </select>
                </div>
            `).join('');
            const countryOptionsHTML = this.countries && this.countries.length > 0
                ? this.generateCountryOptions()
                : `<option value="" disabled selected>Could not load countries</option>`;
            searchContainer.innerHTML = `
                <div class="nt-search-header">
                    <h2>NT Player Search</h2>
                    <button class="nt-search-results-close" title="Close Panel (Esc)" style="font-size: 1.2rem; padding: 0.3rem 0.6rem;">×</button>
                </div>
                <div class="nt-search-grid">
                    ${skillsHTML}
                    <div class="nt-search-field">
                        <label title="Minimum Total Balls">Total Balls</label>
                        <select name="totalBalls" title="Select minimum Total Balls">
                            ${this.generateOptions(110, 9, 'totalBalls')}
                        </select>
                    </div>
                    <div class="nt-search-field">
                        <label title="Minimum Age">Min Age</label>
                        <select name="minAge" title="Select minimum Age">
                            ${this.generateOptions(96, 16, 'minAge')}
                        </select>
                    </div>
                    <div class="nt-search-field">
                        <label title="Maximum Age">Max Age</label>
                        <select name="maxAge" title="Select maximum Age">
                            ${this.generateOptions(96, 16, 'maxAge')}
                        </select>
                    </div>
                    <div class="nt-search-country-select nt-search-field">
                        <label>Country</label>
                        <select name="country" required title="Select country (only your country is enabled)">
                            ${countryOptionsHTML}
                        </select>
                    </div>
                </div>
                <div class="nt-search-buttons">
                    <button class="nt-search-button" title="Start searching">${goText}</button>
                    <button class="nt-search-results-button" style="display: none;" title="Show found players">Show Results</button>
                </div>
                <div class="nt-search-log" title="Search process log"></div>
                <a href="https://www.managerzone.com/?p=guestbook&uid=8577497"
                   class="nt-search-guestbook-link"
                   target="_blank"
                   title="Visit Author's Guestbook">
                    <i class="fa fa-book" aria-hidden="true"></i>
                </a>`;
            document.body.appendChild(searchContainer);
            const panelCloseButton = searchContainer.querySelector('.nt-search-header .nt-search-results-close');
             if (panelCloseButton) {
                 panelCloseButton.addEventListener('click', () => {
                     searchContainer.classList.remove('visible');
                 });
             }
             return true;
        }
        generateCountryOptions() {
             if (!this.countries || this.countries.length === 0) {
                 return `<option value="" disabled selected>Error loading countries</option>`;
             }
             const placeholder = `<option value="" disabled ${!this.userCountry ? 'selected' : ''}>Select country</option>`;
             const options = this.countries
                .sort((a, b) => a.name.localeCompare(b.name))
                .map(country => {
                     const isUserCountry = country.code === this.userCountry;
                     const displayName = country.name === 'Czech Republic' ? 'Czechia' :
                         country.name === 'Macedonia' ? 'North Macedonia' : country.name;
                     const selectedAttr = isUserCountry ? 'selected' : '';
                     const disabledAttr = !isUserCountry ? 'disabled' : '';
                     return `
                         <option value="${country.code}"
                                 data-ntid="${country.ntid}"
                                 data-cid="${country.cid}"
                                 ${selectedAttr}
                                 ${disabledAttr}>
                             ${displayName}
                         </option>`;
                 }).join('');
             return placeholder + options;
        }
        generateOptions(max, min = 0, name) {
             let optionsHTML = '';
             const defaultValue = this.searchValues[name];
             for (let i = min; i <= max; i++) {
                 const selected = (defaultValue === i) ? 'selected' : '';
                 optionsHTML += `<option value="${i}" ${selected}>${i}</option>`;
             }
             return optionsHTML;
        }
        handleSelectChange(e) {
            const select = e.target;
            const value = select.value;
            if (select.name === 'country') {
                const option = select.selectedOptions[0];
                 if (option && option.value) {
                     this.searchValues.country = value;
                     this.searchValues.countryData = {
                         ntid: option.dataset.ntid,
                         cid: option.dataset.cid
                     };
                     if(this.logger) this.logger.log(`Country set to: ${option.textContent.trim()}`);
                 } else {
                     this.searchValues.country = '';
                     this.searchValues.countryData = null;
                     if(this.logger) this.logger.log(`Country selection cleared.`, 'warn');
                 }
            } else {
                 const numValue = parseInt(value);
                 if (!isNaN(numValue)) {
                    this.searchValues[select.name] = numValue;
                     if (select.name === 'minAge' && numValue > this.searchValues.maxAge) {
                         this.searchValues.maxAge = numValue;
                         const maxAgeSelect = document.querySelector('select[name="maxAge"]');
                         if (maxAgeSelect) maxAgeSelect.value = numValue;
                     } else if (select.name === 'maxAge' && numValue < this.searchValues.minAge) {
                         this.searchValues.minAge = numValue;
                         const minAgeSelect = document.querySelector('select[name="minAge"]');
                         if (minAgeSelect) minAgeSelect.value = numValue;
                     }
                 }
            }
        }
        setUpEvents() {
            const openButton = document.querySelector('.nt-search-open-btn');
            const searchContainer = document.querySelector('.nt-search-container');
            if (!searchContainer) {
                 console.error("Search container not found for event setup.");
                 return;
             }
            const internalSearchButton = searchContainer.querySelector('.nt-search-button');
            const resultsButton = searchContainer.querySelector('.nt-search-results-button');
            const selects = searchContainer.querySelectorAll('select');
            const panelCloseButton = searchContainer.querySelector('.nt-search-header .nt-search-results-close');
            if (openButton) {
                 openButton.addEventListener('click', (e) => {
                    e.preventDefault();
                    e.stopPropagation();
                     if (searchContainer) {
                        searchContainer.classList.add('visible');
                    } else {
                         console.error("Search container not found when trying to open.");
                     }
                });
            } else {
                console.error("Open button not found during event setup.");
            }
            if (!panelCloseButton?.onclick && panelCloseButton) {
                panelCloseButton.addEventListener('click', () => {
                    if (searchContainer) searchContainer.classList.remove('visible');
                });
             }
            document.addEventListener('click', (e) => {
                 if (searchContainer?.classList.contains('visible') &&
                    !searchContainer.contains(e.target) &&
                    !openButton?.contains(e.target)) {
                     searchContainer.classList.remove('visible');
                 }
             });
            document.addEventListener('keydown', (e) => {
                 if (e.key === 'Escape' && searchContainer?.classList.contains('visible')) {
                     searchContainer.classList.remove('visible');
                 }
            });
            if(selects.length > 0){
                 selects.forEach(select => {
                    if (this.searchValues.hasOwnProperty(select.name)) {
                       select.value = this.searchValues[select.name];
                    }
                    select.addEventListener('change', (e) => this.handleSelectChange(e));
                });
                 const countrySelect = searchContainer.querySelector('select[name="country"]');
                 if(countrySelect && this.searchValues.country){
                    countrySelect.value = this.searchValues.country;
                 } else if(countrySelect && !this.userCountry) {
                     countrySelect.selectedIndex = 0;
                 }
             } else {
                 console.error("Select elements not found inside search container.");
             }
            if (internalSearchButton) {
                 internalSearchButton.addEventListener('click', () => this.performSearch());
            } else {
                 console.error("Internal search button not found.");
             }
            if (resultsButton) {
                resultsButton.addEventListener('click', () => this.showResults());
            } else {
                 console.error("Results button not found.");
            }
        }
    }
    try {
        const searcher = new NTPlayerSearcher();
        if (document.readyState === 'interactive' || document.readyState === 'complete') {
            searcher.init();
        } else {
            document.addEventListener('DOMContentLoaded', () => searcher.init());
        }
    } catch (e) {
         console.error(e);
         alert("Failed to initialize NTPlayerSearch. Check the console for details.");
    }
})();