MZ - ClashPotentialScore/esperiano

Calculates max clash score and analyzes member status for a specific, user-configurable federation

// ==UserScript==
// @name         MZ - ClashPotentialScore/esperiano
// @namespace    douglaskampl
// @version      1.0
// @description  Calculates max clash score and analyzes member status for a specific, user-configurable federation
// @author       Douglas
// @match        https://www.managerzone.com/?p=federations&sub=clash*
// @grant        none
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const STORAGE_KEY = 'esperiano__config';
    const DEFAULT_CONFIG = {
        federationName: "Juventus FC",
        members: [
            'datch', 'esperiano', 'dadda4d', 'killermanager_91', 'toti1102',
            'fortalexis', 'manuchobot', 'sliemafc', 'tobi10', 'troyh65',
            'alexjas', 'edubeltro', 'fare_hadzibegic', 'southpacific',
            'new188', 'juventud1993'
        ]
    };

    function getConfig() {
        try {
            const storedConfig = localStorage.getItem(STORAGE_KEY);
            if (storedConfig) {
                return JSON.parse(storedConfig);
            } else {
                localStorage.setItem(STORAGE_KEY, JSON.stringify(DEFAULT_CONFIG));
                return DEFAULT_CONFIG;
            }
        } catch (error) {
            return DEFAULT_CONFIG;
        }
    }

    function saveConfig(configObject) {
        try {
            const sanitizedConfig = {
                federationName: configObject.federationName.trim(),
                members: configObject.members.map(m => m.trim()).filter(m => m.length > 0)
            };
            localStorage.setItem(STORAGE_KEY, JSON.stringify(sanitizedConfig));
            return true;
        } catch (error) {
            return false;
        }
    }

    function extractTitleData() {
        const title = document.title;
        const match = title.match(/\|\s*(.*?)\s+vs\s+/);
        if (match && match[1]) {
            return { pageFed1Name: match[1].trim() };
        }
        return { pageFed1Name: null };
    }

    function parseAllChallenges(tableElement) {
        const definitiveChallenges = [];
        const attackersWhoPlayed = new Set();
        const rows = tableElement.querySelectorAll('tbody > tr');

        rows.forEach((row) => {
            const challengeItemsContainer = row.querySelector('td:last-of-type .challenges');
            if (!challengeItemsContainer) return;

            const challengeItems = challengeItemsContainer.querySelectorAll('.challenges__item');
            if (challengeItems.length === 0) return;

            challengeItems.forEach(item => {
                const attackerNameElement = item.querySelector('.challenges--name .username');
                if (attackerNameElement) {
                    attackersWhoPlayed.add(attackerNameElement.textContent.trim());
                }
            });

            let finalItem = challengeItems[0];
            if (challengeItems.length > 1) {
                finalItem = Array.from(challengeItems).find(item => !item.querySelector('.challenges__older')) || finalItem;
            }

            const mainAttackerNameElement = finalItem.querySelector('.challenges--name .username');
            const resultLink = finalItem.querySelector('a[href*="p=match"]');

            if (!mainAttackerNameElement || !resultLink) return;

            const defenderCell = row.querySelector('td:nth-child(8) a.username');
            const defenderName = defenderCell ? defenderCell.textContent.trim() : 'Unknown Defender';
            const mainAttackerName = mainAttackerNameElement.textContent.trim();

            let result;
            if (resultLink.classList.contains('challenges--win')) result = 'WIN';
            else if (resultLink.classList.contains('challenges--lost')) result = 'LOSS';
            else result = 'DRAW';

            const totalAttemptsOnRow = Array.from(challengeItems).reduce((acc, item) => acc + (item.querySelector('.challenges--prev') ? 2 : 1), 0);

            definitiveChallenges.push({
                defender: defenderName,
                attacker: mainAttackerName,
                result: result,
                totalAttempts: totalAttemptsOnRow,
                isKeyMemberIntervention: challengeItems.length > 1
            });
        });

        return { definitiveChallenges, attackersWhoPlayed };
    }

    function calculatePotentialScore(tableElement, definitiveChallenges) {
        const points = { WIN: 4, DRAW: 2, LOSS: 1 };
        let currentScore = 0;

        const totalDefenderRows = tableElement.querySelectorAll('tbody > tr').length;
        currentScore += Math.max(0, 16 - totalDefenderRows) * points.WIN;
        definitiveChallenges.forEach(match => currentScore += points[match.result]);

        let potentialScore = currentScore;
        const opportunities = { secondAttempt: [], keyMember: [] };
        const keyMembersUsed = definitiveChallenges.filter(m => m.isKeyMemberIntervention).length;

        definitiveChallenges.filter(m => m.result !== 'WIN' && m.totalAttempts === 1).forEach(match => {
            const potentialGain = points.WIN - points[match.result];
            potentialScore += potentialGain;
            opportunities.secondAttempt.push({ ...match, potentialGain });
        });

        const availableKeyMembers = 2 - keyMembersUsed;
        if (availableKeyMembers > 0) {
            const keyMemberTargets = definitiveChallenges
            .filter(m => m.result !== 'WIN' && m.totalAttempts >= 2 && !m.isKeyMemberIntervention)
            .map(m => ({ ...m, potentialGain: points.WIN - points[m.result] }))
            .sort((a, b) => b.potentialGain - a.potentialGain);

            const bestTargets = keyMemberTargets.slice(0, availableKeyMembers);
            bestTargets.forEach(target => {
                potentialScore += target.potentialGain;
            });

            if (keyMemberTargets.length > 0) {
                opportunities.keyMember = keyMemberTargets;
            }
        }

        potentialScore += Math.max(0, totalDefenderRows - definitiveChallenges.length) * points.WIN;
        return { currentScore, potentialScore, keyMembersUsed, opportunities };
    }

    function renderScores(scores, memberStatus, pageFed1Name) {
        const existingContainer = document.querySelector('.cpan-score-container');
        if (existingContainer) existingContainer.remove();

        const scoreContainer = document.createElement('div');
        scoreContainer.className = 'flex-grow-1 cpan-score-container';
        scoreContainer.innerHTML = `<span class="cpan-score-text">${pageFed1Name} MaxScore: <strong>${scores.potentialScore}</strong></span><span class="cpan-help-icon">?</span>`;

        scoreContainer.querySelector('.cpan-help-icon').addEventListener('click', (event) => {
            event.stopPropagation();
            bsModal(scores, memberStatus);
        });

        const placementAnchor = document.querySelector('#federation-select');
        const parentContainer = placementAnchor ? placementAnchor.closest('.flex-grow-1').parentNode : document.querySelector('.top-pane__name-score');
        if (parentContainer) {
            setTimeout(() => {
                parentContainer.appendChild(scoreContainer);
                setTimeout(() => scoreContainer.style.opacity = '1', 100);
            }, 1234);
        }
    }

    function bsModal(scores, memberStatus) {
        if (document.querySelector('.cpan-modal-overlay')) return;

        const overlay = document.createElement('div');
        overlay.className = 'cpan-modal-overlay';
        const modalContent = document.createElement('div');
        modalContent.className = 'cpan-modal-content';

        let opportunitiesHtml = '';
        if (scores.opportunities.secondAttempt.length > 0) {
            opportunitiesHtml += '<h4>2nd Attempt</h4><ul>';
            scores.opportunities.secondAttempt.forEach(op => {
                opportunitiesHtml += `<li><strong>${op.attacker}</strong> can play a 2nd match (Potential gain: <strong>+${op.potentialGain} pts</strong>)</li>`;
            });
            opportunitiesHtml += '</ul>';
        }

        const availableKeyMembers = 2 - scores.keyMembersUsed;
        if (scores.opportunities.keyMember.length > 0 && availableKeyMembers > 0) {
            opportunitiesHtml += `<h4>Key Members</h4>`;

            const allCandidateNames = scores.opportunities.keyMember.map(op => `<strong>${op.attacker}</strong>`).join('/');
            const bestChoices = scores.opportunities.keyMember.slice(0, availableKeyMembers);
            const maxGainFromKeyMembers = bestChoices.reduce((sum, op) => sum + op.potentialGain, 0);

            opportunitiesHtml += '<ul>';
            opportunitiesHtml += `<li>${allCandidateNames} (Potential gain: <strong>+${maxGainFromKeyMembers} pts</strong>)</li>`;
            opportunitiesHtml += '</ul>';
        }

        if (opportunitiesHtml === '') {
            opportunitiesHtml = '<p>No further opportunities identified.</p>';
        }

        let memberStatusSectionHtml = '';
        if (memberStatus) {
            let memberStatusListHtml = '<ul>';
            memberStatus.forEach(item => {
                const icon = item.played ? 'fa-check-circle cpan-icon-played' : 'fa-times-circle cpan-icon-not-played';
                memberStatusListHtml += `<li><i class="fa ${icon}"></i> ${item.name}</li>`;
            });
            memberStatusListHtml += '</ul>';
            memberStatusSectionHtml = `
            <hr>
            <div id="cpan-member-status-section">
                <h4 id="cpan-member-status-toggle">
                    <span class="cpan-toggle-title"><i class="fa fa-chevron-down" id="cpan-status-chevron"></i> Stato Membri</span>
                </h4>
                <div id="cpan-member-status-display">${memberStatusListHtml}</div>
            </div>`;
        }

        modalContent.innerHTML = `
    <div class="cpan-modal-header">
        <span id="cpan-main-title">Opportunità</span>
        <span id="cpan-edit-title" style="display: none;"><i class="fa fa-arrow-left cpan-back-icon"></i> Modifica Configurazione</span>
        <i class="fa fa-cog cpan-global-config-icon" title="Modifica Configurazione"></i>
    </div>
    <div class="cpan-modal-body">
        <div id="cpan-main-view">
            <p>Current Score: <strong>${scores.currentScore}</strong> | Max Potential Score: <strong>${scores.potentialScore}</strong> | Key Members Used: <strong>${scores.keyMembersUsed} / 2</strong></p>
            <hr>
            ${opportunitiesHtml}
            ${memberStatusSectionHtml}
        </div>
        <div id="cpan-edit-config-view" style="display: none;">
            <label for="cpan-fed-name-input">Nome Federazione</label>
            <input type="text" id="cpan-fed-name-input" />
            <label for="cpan-members-textarea">Elenco Membri</label>
            <textarea id="cpan-members-textarea" rows="8"></textarea>
            <div class="cpan-button-group">
                <button id="cpan-save-config-btn" class="cpan-modal-btn cpan-modal-btn-primary">Salva</button>
            </div>
        </div>
    </div>`;

        overlay.appendChild(modalContent);
        document.body.appendChild(overlay);

        const config = getConfig();
        const mainView = modalContent.querySelector('#cpan-main-view');
        const editView = modalContent.querySelector('#cpan-edit-config-view');
        const globalConfigIcon = modalContent.querySelector('.cpan-global-config-icon');
        const backIcon = modalContent.querySelector('.cpan-back-icon');
        const saveBtn = modalContent.querySelector('#cpan-save-config-btn');
        const fedNameInput = modalContent.querySelector('#cpan-fed-name-input');
        const membersTextarea = modalContent.querySelector('#cpan-members-textarea');
        const mainTitle = modalContent.querySelector('#cpan-main-title');
        const editTitle = modalContent.querySelector('#cpan-edit-title');

        const showEditView = () => {
            mainView.style.display = 'none';
            editView.style.display = 'block';
            mainTitle.style.display = 'none';
            editTitle.style.display = 'flex';
            globalConfigIcon.style.display = 'none';
            fedNameInput.value = config.federationName;
            membersTextarea.value = config.members.join('\n');
        };

        const hideEditView = () => {
            editView.style.display = 'none';
            mainView.style.display = 'block';
            editTitle.style.display = 'none';
            mainTitle.style.display = 'inline';
            globalConfigIcon.style.display = 'inline-block';
        };

        globalConfigIcon.addEventListener('click', showEditView);
        backIcon.addEventListener('click', hideEditView);

        saveBtn.addEventListener('click', () => {
            const newConfig = {
                federationName: fedNameInput.value,
                members: membersTextarea.value.split('\n')
            };
            if (saveConfig(newConfig)) {
                window.location.reload();
            }
        });

        if (memberStatus) {
            const toggleHeader = modalContent.querySelector('#cpan-member-status-toggle');
            const chevron = modalContent.querySelector('#cpan-status-chevron');
            const statusDisplay = modalContent.querySelector('#cpan-member-status-display');

            statusDisplay.style.display = 'none';
            chevron.style.transform = 'rotate(-90deg)';

            toggleHeader.addEventListener('click', () => {
                const isHidden = statusDisplay.style.display === 'none';
                statusDisplay.style.display = isHidden ? 'block' : 'none';
                chevron.style.transform = isHidden ? 'rotate(0deg)' : 'rotate(-90deg)';
            });
        }

        overlay.addEventListener('click', (event) => { if (event.target === overlay) overlay.remove() });

        requestAnimationFrame(() => overlay.classList.add('visible'));
    }

    function runClashAnalysis() {
        const table = document.querySelector('.hitlist.challenges-list');
        if (!table) return;

        const { pageFed1Name } = extractTitleData();
        if (!pageFed1Name) return;

        const config = getConfig();
        const isTargetFederation = config.federationName.trim().toLowerCase() === pageFed1Name.toLowerCase();

        const { definitiveChallenges, attackersWhoPlayed } = parseAllChallenges(table);
        const scores = calculatePotentialScore(table, definitiveChallenges);

        let memberStatus = null;
        if (isTargetFederation) {
            memberStatus = config.members.map(name => ({
                name,
                played: attackersWhoPlayed.has(name)
            }));
        }
        renderScores(scores, memberStatus, pageFed1Name);
    }

    function applyStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .cpan-score-container { opacity: 0; transition: opacity 0.5s ease-in-out; display: flex; align-items: center; justify-content: flex-end; text-align: right; align-self: center; }
            .cpan-score-text { font-family: 'Plus Jakarta Sans', sans-serif; font-size: 1.1em; font-weight: 500; color: #152743; text-shadow: 0 0 5px rgba(0,0,0,0.5); }
            .cpan-score-text strong { font-weight: 700; color: #3498db; }
            .cpan-help-icon { font-family: 'Space Grotesk', monospace; font-weight: 700; font-size: 0.8em; color: #FFFFFF; background-color: #3498db; border-radius: 50%; width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; margin-left: 8px; cursor: pointer; transition: transform 0.2s ease, box-shadow 0.2s ease; }
            .cpan-help-icon:hover { transform: scale(1.1); box-shadow: 0 0 10px rgba(52, 152, 219, 0.7); }
            .cpan-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(21, 39, 67, 0.7); backdrop-filter: blur(5px); z-index: 10000; display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s ease-in-out; }
            .cpan-modal-overlay.visible { opacity: 1; }
            .cpan-modal-content { font-family: 'Space Grotesk', sans-serif; background-color: #F4F6F8; padding: 0; border-radius: 8px; box-shadow: 0 5px 20px rgba(0,0,0,0.2); width: 90%; max-width: 550px; position: relative; transform: scale(0.95); transition: transform 0.3s ease-in-out; overflow: hidden;}
            .cpan-modal-overlay.visible .cpan-modal-content { transform: scale(1); }
            .cpan-modal-header { display: flex; justify-content: space-between; align-items: center; padding: 15px 25px; background-color: #EAECEE; border-bottom: 1px solid #DDE3E9; font-size: 1.5em; font-weight: 700; color: #152743; }
            .cpan-global-config-icon, .cpan-back-icon { cursor: pointer; color: #7f8c8d; transition: color 0.2s, transform 0.3s; }
            .cpan-global-config-icon:hover { color: #1D2B3A; transform: rotate(45deg); }
            .cpan-back-icon { margin-right: 15px; }
            .cpan-back-icon:hover { color: #1D2B3A; transform: translateX(-3px); }
            #cpan-edit-title { align-items: center; }
            .cpan-modal-body { padding: 15px 25px 25px 25px; }
            .cpan-modal-body hr { border: none; height: 1px; background-color: #DDE3E9; margin: 15px 0; }
            .cpan-modal-body h4, .cpan-modal-body h5 { font-size: 1.1em; color: #2c3e50; margin-top: 15px; margin-bottom: 10px; display: flex; align-items: center; }
            .cpan-subtitle { font-size: 0.9em; color: #555; margin-top: -8px; margin-bottom: 12px; }
            #cpan-member-status-toggle { cursor: pointer; justify-content: flex-start; }
            #cpan-status-chevron { transition: transform 0.3s; }
            .cpan-toggle-title { display: flex; align-items: center; gap: 8px; }
            .cpan-modal-body ul { list-style-type: none; padding-left: 0; margin: 0; }
            .cpan-modal-body li { background-color: #EAECEE; padding: 8px 12px; border-radius: 4px; margin-bottom: 6px; font-size: 0.95em; display: flex; align-items: center; }
            .cpan-modal-body li > i { margin-right: 10px; font-size: 1.2em; width: 20px; text-align: center; }
            .cpan-icon-played { color: #27ae60; }
            .cpan-icon-not-played { color: #c0392b; }
            .cpan-modal-body p { font-size: 1em; color: #34495E; line-height: 1.6; margin: 0 0 12px 0; }
            .cpan-modal-body strong { font-weight: 700; color: #3498db; }
            #cpan-edit-config-view label { display: block; margin-top: 10px; margin-bottom: 5px; font-size: 0.9em; color: #34495E; font-weight: bold; }
            #cpan-edit-config-view input[type="text"], #cpan-edit-config-view textarea { width: 98%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-family: inherit; background-color: #fff; }
            .cpan-button-group { text-align: right; margin-top: 15px; }
            .cpan-modal-btn { padding: 10px 20px; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 0.9em; text-transform: uppercase; letter-spacing: 0.5px; transition: all 0.2s ease; margin-left: 10px;}
            .cpan-modal-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
            .cpan-modal-btn-primary { background-color: #1D2B3A; color: white; border: 1px solid #1D2B3A; }
            .cpan-modal-btn-primary:hover { background-color: #2C3E50; }
        `;
        document.head.appendChild(style);
    }

    const observer = new MutationObserver((mutations, obs) => {
        const table = document.querySelector('.hitlist.challenges-list');
        const placementAnchor = document.querySelector('#federation-select') || document.querySelector('.top-pane__name-score');

        if (table && placementAnchor) {
            runClashAnalysis();
            applyStyles();
            obs.disconnect();
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();