Torn Chain Payout Calculator

Calculates payouts, generates reports, and manages API keys in compliance with Torn's ToS. Supports auto-config for Torn PDA.

// ==UserScript==
// @name         Torn Chain Payout Calculator
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Calculates payouts, generates reports, and manages API keys in compliance with Torn's ToS. Supports auto-config for Torn PDA.
// @match        https://www.torn.com/war.php?step=chainreport&chainID=*
// @match        https://www.torn.com/factions.php*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @connect      api.torn.com
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- PROMISE-BASED GM STORAGE HELPERS ---
    const GM_get = (key, defaultValue) => new Promise(resolve => resolve(GM_getValue(key, defaultValue)));
    const GM_set = (key, value) => new Promise(resolve => resolve(GM_setValue(key, value)));
    const GM_del = (key) => new Promise(resolve => resolve(GM_deleteValue(key)));
    const API_KEY_STORAGE_KEY = 'chain_payout_api_key';

    // --- PDA DETECTION ---
    const isPDA = typeof window.flutter_inappwebview !== 'undefined' &&
                  typeof window.flutter_inappwebview.callHandler === 'function';

    // --- PAGE ROUTER ---
    if (window.location.href.includes('war.php?step=chainreport&chainID=')) {
        setupChainReportPage();
    } else if (window.location.href.includes('factions.php')) {
        setupFactionPage();
    }

    // =================================================================================
    // --- FACTION PAGE LOGIC ---
    // =================================================================================
    function setupFactionPage() {
        async function markPaidChains() {
            const newsList = document.querySelector('ul.listWrapper___lJjf7');
            if (!newsList) return;

            const chainLinks = newsList.querySelectorAll('p.message___RSW3S a[href*="chainID="]');
            for (const link of chainLinks) {
                try {
                    const chainID = new URL(link.href, window.location.origin).searchParams.get('chainID');
                    if (!chainID) continue;

                    const storageKey = `payout_status_${chainID}`;
                    const isPaid = await GM_get(storageKey, false);
                    const messageElement = link.parentElement;

                    if (messageElement) {
                        const existingIndicator = messageElement.querySelector('.paid-status-indicator');
                        if (isPaid && !existingIndicator) {
                            const paidSpan = document.createElement('span');
                            paidSpan.textContent = ' [PAID]';
                            paidSpan.style.color = '#4CAF50';
                            paidSpan.style.fontWeight = 'bold';
                            paidSpan.className = 'paid-status-indicator';
                            messageElement.appendChild(paidSpan);
                        } else if (!isPaid && existingIndicator) {
                            existingIndicator.remove();
                        }
                    }
                } catch (e) {
                    console.error("Torn Payout Script: Error processing chain link.", e);
                }
            }
        }

        function waitForElement(selector) {
            return new Promise(resolve => {
                if (document.querySelector(selector)) return resolve(document.querySelector(selector));
                const observer = new MutationObserver(() => {
                    if (document.querySelector(selector)) {
                        observer.disconnect();
                        resolve(document.querySelector(selector));
                    }
                });
                observer.observe(document.body, { childList: true, subtree: true });
            });
        }

        waitForElement('#factions').then(factionsContainer => {
            const observer = new MutationObserver(() => {
                clearTimeout(window.factionNewsCheckTimeout);
                window.factionNewsCheckTimeout = setTimeout(markPaidChains, 250);
            });
            observer.observe(factionsContainer, { childList: true, subtree: true });
            markPaidChains();
        });
    }

    // =================================================================================
    // --- CHAIN REPORT PAGE LOGIC (UPDATED FOR API COMPLIANCE & PDA SUPPORT) ---
    // =================================================================================
    function setupChainReportPage() {
        const XANAX_COST = 840000;
        const ATTACKS_PER_XANAX = 10;
        const urlParams = new URLSearchParams(window.location.search);
        const chainID = urlParams.get('chainID');
        if (!chainID || isNaN(chainID)) return;

        const style = document.createElement('style');
        style.textContent = `
            /* Main Payout Container */
            #payout-container {
                background-color: #333; border-radius: 5px; padding: 15px; margin-top: 10px;
                border: 1px solid #222; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
            }
            #payout-container h2 {
                font-size: 1.1em; color: #ddd; font-weight: bold; padding-bottom: 10px;
                margin-bottom: 10px; border-bottom: 1px solid #444; margin-top: 0;
            }
            #payout-container > p { font-size: 0.9em; color: #bbb; margin: 0 0 15px 0; }
            #payout-container .payout-table-wrapper {
                overflow-x: auto; background-color: #2c2c2c; border: 1px solid #444;
                border-radius: 5px; margin-bottom: 15px;
            }
            #payout-container table { border-collapse: collapse; width: 100%; }
            #payout-container th {
                background-color: #4CAF50; color: white; padding: 10px; text-align: left;
                border-bottom: 2px solid #444; white-space: nowrap;
            }
            #payout-container td {
                padding: 8px; text-align: left; border-bottom: 1px solid #333; color: white;
                white-space: nowrap; cursor: pointer;
            }
            #payout-container td.cell-copied {
                background-color: #007bff !important; color: white !important;
                transition: background-color 0.1s ease-in-out;
            }
            #payout-container tr:nth-child(even) { background-color: #333; }
            #payout-container tr:hover { background-color: #3d3d3d; }
            #payout-container .total-row { font-weight: bold; background-color: #4CAF50; color: white; }
            #payout-container .total-row td { cursor: default; }
            #payout-container .button-group { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 15px; }
            #payout-container .action-button {
                padding: 8px 16px; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 0.9em;
            }
            #payout-container .close-button { background-color: #ff4444; }
            #payout-container .close-button:hover { background-color: #cc0000; }
            #payout-container .copy-button { background-color: #007bff; }
            #payout-container .copy-button:hover { background-color: #0056b3; }
            #payout-container .mark-paid-button { background-color: #ffc107; color: black; }
            #payout-container .mark-paid-button:hover { background-color: #d39e00; }
            #payout-container .mark-paid-button:disabled { background-color: #6c757d; cursor: not-allowed; }
            #payout-container .clickable-name { cursor: pointer; text-decoration: underline; color: #87ceeb; }
            #payout-container #paid-out-warning {
                color: #ffc107; font-weight: bold; font-size: 1em; text-align: center;
                padding: 10px; border: 1px solid #ffc107; border-radius: 5px; margin-bottom: 15px;
            }
            #payout-container #individual-report-container {
                margin-top: 10px; padding: 15px; border: 1px dashed #555; border-radius: 5px; background-color: #252525;
            }
            #payout-container #individual-report-container h4 { margin-top: 0; font-size: 1em; }

            /* API Key Management UI */
            #api-key-overlay {
                position: fixed; top: 0; left: 0; width: 100%; height: 100%;
                background-color: rgba(0,0,0,0.7); z-index: 9998;
                display: flex; align-items: center; justify-content: center;
            }
            #api-key-container {
                background-color: #333; border-radius: 5px; padding: 20px;
                border: 1px solid #222; box-shadow: 0 2px 10px rgba(0,0,0,0.5);
                width: 90%; max-width: 650px; color: white;
            }
            #api-key-container h3 { margin-top: 0; color: #4CAF50; border-bottom: 1px solid #444; padding-bottom: 10px; }
            #api-key-container p { font-size: 0.9em; color: #ccc; }
            #api-key-container .tos-table { width: 100%; border-collapse: collapse; font-size: 0.8em; margin: 15px 0; }
            #api-key-container .tos-table th, #api-key-container .tos-table td { border: 1px solid #555; padding: 6px; text-align: left; }
            #api-key-container .tos-table th { background-color: #444; }
            #api-key-container .tos-table code { background: #222; padding: 2px 4px; border-radius: 3px; }
            #api-key-container input[type="password"] {
                width: 100%; padding: 8px; border-radius: 3px; border: 1px solid #555;
                background-color: #222; color: white; margin-bottom: 15px; box-sizing: border-box;
            }
            #api-key-container .api-button-group { display: flex; gap: 10px; justify-content: flex-end; }
            #api-key-container .api-button { padding: 8px 16px; border: none; border-radius: 5px; cursor: pointer; }
            #api-key-container .save-key { background-color: #4CAF50; color: white; }
            #api-key-container .clear-key { background-color: #f44336; color: white; }
            #api-key-container .cancel-key { background-color: #6c757d; color: white; }

            /* General UI Elements */
            .spinner {
                border: 4px solid rgba(0, 0, 0, 0.1); border-left-color: #4CAF50; border-radius: 50%;
                width: 24px; height: 24px; animation: spin 1s linear infinite;
                position: fixed; bottom: 50px; left: 160px; z-index: 1001;
            }
            .fixed-button {
                position: fixed; bottom: 50px; z-index: 1000;
                color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer;
            }
            @keyframes spin { to { transform: rotate(360deg); } }
        `;
        document.head.appendChild(style);

        const calculateButton = document.createElement('button');
        calculateButton.textContent = 'Calculate Payout';
        calculateButton.className = 'fixed-button';
        calculateButton.style.left = '10px';
        calculateButton.style.backgroundColor = '#4CAF50';
        document.body.appendChild(calculateButton);

        const manageApiButton = document.createElement('button');
        manageApiButton.textContent = 'Manage API Key';
        manageApiButton.className = 'fixed-button';
        manageApiButton.style.left = '180px';
        manageApiButton.style.backgroundColor = '#007bff';
        document.body.appendChild(manageApiButton);

        const gmFetch = (url) => new Promise((resolve, reject) => {
            GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'accept': 'application/json' }, onload: resolve, onerror: reject });
        });

        async function createApiManagementUI() {
            const existingUI = document.getElementById('api-key-overlay');
            if (existingUI) existingUI.remove();

            const overlay = document.createElement('div');
            overlay.id = 'api-key-overlay';

            const currentKey = await GM_get(API_KEY_STORAGE_KEY, '');

            overlay.innerHTML = `
                <div id="api-key-container">
                    <h3>API Key Management</h3>
                    <p>To calculate payouts, this script requires a Torn API key with 'Limited Access'. Your key is stored securely and locally in your browser and is never shared.</p>
                    <table class="tos-table">
                        <thead><tr><th>Data Storage</th><th>Data Sharing</th><th>Purpose of Use</th><th>Key Storage & Sharing</th><th>Key Access Level</th></tr></thead>
                        <tbody><tr>
                            <td>Only locally</td>
                            <td>Nobody</td>
                            <td>Non-malicious statistical analysis</td>
                            <td>Stored locally / Not shared</td>
                            <td>Limited Access: <code>faction (chainreport, balance)</code></td>
                        </tr></tbody>
                    </table>
                    <input type="password" id="api-key-input" placeholder="Enter your Limited Access API Key" value="${currentKey}">
                    <div class="api-button-group">
                        <button class="api-button cancel-key">Cancel</button>
                        <button class="api-button clear-key">Clear Key</button>
                        <button class="api-button save-key">Save Key</button>
                    </div>
                </div>
            `;
            document.body.appendChild(overlay);

            const apiKeyInput = overlay.querySelector('#api-key-input');
            if (isPDA && currentKey === '###PDA-APIKEY###') {
                apiKeyInput.disabled = true;
                const pdaNote = document.createElement('p');
                pdaNote.style.color = '#87ceeb';
                pdaNote.textContent = 'Using Torn PDA managed API key. To change, edit in Torn PDA settings.';
                apiKeyInput.insertAdjacentElement('afterend', pdaNote);
            }

            const closeUI = () => document.getElementById('api-key-overlay')?.remove();

            overlay.querySelector('.save-key').addEventListener('click', async () => {
                const newKey = overlay.querySelector('#api-key-input').value.trim();
                if (newKey) {
                    await GM_set(API_KEY_STORAGE_KEY, newKey);
                    alert('API Key saved!');
                    closeUI();
                } else {
                    alert('API Key cannot be empty. Use the "Clear Key" button to remove it.');
                }
            });

            overlay.querySelector('.clear-key').addEventListener('click', async () => {
                if (confirm('Are you sure you want to clear your stored API key?')) {
                    await GM_del(API_KEY_STORAGE_KEY);
                    alert('API Key cleared.');
                    closeUI();
                }
            });

            overlay.querySelector('.cancel-key').addEventListener('click', closeUI);
        }

        manageApiButton.addEventListener('click', createApiManagementUI);

        calculateButton.addEventListener('click', async () => {
            let apiKey = await GM_get(API_KEY_STORAGE_KEY, '');

            if (!apiKey) {
                if (isPDA) {
                    await GM_set(API_KEY_STORAGE_KEY, '###PDA-APIKEY###');
                    apiKey = '###PDA-APIKEY###';
                    alert('Torn PDA detected. API key has been automatically configured.');
                } else {
                    alert('No API key found. Please set your key first.');
                    await createApiManagementUI();
                    return;
                }
            }

            const bcrInput = prompt('Enter the Base Chain Reward (BCR) for performance payout (excluding Xanax costs):', '1000000');
            const BCR = parseFloat(bcrInput);
            if (isNaN(BCR) || BCR < 0) { alert('Invalid BCR value.'); return; }

            const spinner = document.createElement('div');
            spinner.className = 'spinner';
            document.body.appendChild(spinner);

            try {
                const [chainResponse, balanceResponse] = await Promise.all([
                    gmFetch(`https://api.torn.com/v2/faction/${chainID}/chainreport?selections=&key=${apiKey}`),
                    gmFetch(`https://api.torn.com/v2/faction/balance?key=${apiKey}`)
                ]);

                const chainData = JSON.parse(chainResponse.responseText);
                if (chainData.error) { alert(`API Error (Chain Report): ${chainData.error.error}\n\nThis may be an invalid key or insufficient permissions. Click 'Manage API Key' to check.`); return; }
                const balanceData = JSON.parse(balanceResponse.responseText);
                if (balanceData.error) { alert(`API Error (Faction Balance): ${balanceData.error.error}`); return; }

                await calculateAndDisplayPayouts(chainData.chainreport, balanceData, BCR);
            } catch (error) {
                console.error('Torn Payout Script Error:', error);
                alert('A network error occurred. Check your API key and internet connection.');
            } finally {
                if (spinner && document.body.contains(spinner)) document.body.removeChild(spinner);
            }
        });

        async function calculateAndDisplayPayouts(chainReport, balanceData, BCR) {
            const attackers = chainReport.attackers;
            let totalICS = 0;

            const memberDataMap = new Map();
            if (balanceData?.balance?.members) {
                balanceData.balance.members.forEach(member => {
                    memberDataMap.set(member.id, { username: member.username, money: member.money });
                });
            }

            attackers.forEach(attacker => {
                attacker.ICS = (attacker.attacks.total * 5) + (attacker.respect.total * 100);
                totalICS += attacker.ICS;
                const memberInfo = memberDataMap.get(attacker.id);
                attacker.username = memberInfo?.username || `User #${attacker.id}`;
                attacker.currentBalance = memberInfo?.money ?? null;
            });

            let totalFinalPayout = 0;
            attackers.forEach(attacker => {
                attacker.performancePayout = totalICS > 0 ? (BCR * (attacker.ICS / totalICS)) : 0;
                attacker.xanaxReimbursement = (ATTACKS_PER_XANAX > 0 && XANAX_COST > 0) ? (attacker.attacks.total / ATTACKS_PER_XANAX) * XANAX_COST : 0;
                attacker.payout = attacker.performancePayout + attacker.xanaxReimbursement;
                attacker.newBalance = attacker.currentBalance !== null ? attacker.currentBalance + attacker.payout : null;
                totalFinalPayout += attacker.payout;
            });

            const existingContainer = document.getElementById('payout-container');
            if (existingContainer) existingContainer.remove();

            const payoutContainer = document.createElement('div');
            payoutContainer.id = 'payout-container';

            const reportWrapper = document.querySelector('div.chain-report-wrap.chainReportWp___G8B3E');
            if (reportWrapper) {
                reportWrapper.appendChild(payoutContainer);
            } else {
                const mainContainer = document.getElementById('mainContainer') || document.body;
                mainContainer.appendChild(payoutContainer);
            }

            const storageKey = `payout_status_${chainID}`;
            let isPaid = await GM_get(storageKey, false);

            payoutContainer.insertAdjacentHTML('beforeend', `
                <div id="paid-out-warning" style="display: ${isPaid ? 'block' : 'none'};"><strong>This chain has already been marked as paid out.</strong></div>
                <h2>Chain Payout Calculator</h2>
                <p>Base Chain Reward (BCR): ${formatCurrency(BCR)} | Xanax Reimbursement: ${formatCurrency(XANAX_COST)} per ${ATTACKS_PER_XANAX} attacks.</p>
            `);

            const tableWrapper = document.createElement('div');
            tableWrapper.className = 'payout-table-wrapper';

            const table = document.createElement('table');
            table.innerHTML = `<thead><tr>
                <th>Player Name [ID]</th><th>Respect</th><th>Attacks</th><th>Final Payout</th><th>Current Balance</th><th>New Balance</th>
            </tr></thead>`;
            const tbody = document.createElement('tbody');

            attackers.forEach(attacker => {
                tbody.innerHTML += `
                    <tr>
                        <td class="clickable-name" data-id="${attacker.id}">${attacker.username} [${attacker.id}]</td>
                        <td>${attacker.respect.total.toFixed(2)}</td>
                        <td>${attacker.attacks.total}</td>
                        <td>${formatCurrency(attacker.payout)}</td>
                        <td>${attacker.currentBalance !== null ? formatCurrency(attacker.currentBalance) : 'N/A'}</td>
                        <td>${attacker.newBalance !== null ? formatCurrency(attacker.newBalance) : 'N/A'}</td>
                    </tr>
                `;
            });
            table.appendChild(tbody);
            table.insertAdjacentHTML('beforeend', `
                <tfoot><tr class="total-row">
                    <td colspan="3"><strong>Total Final Payout</strong></td>
                    <td><strong>${formatCurrency(totalFinalPayout)}</strong></td>
                    <td colspan="2"></td>
                </tr></tfoot>
            `);

            tableWrapper.appendChild(table);
            payoutContainer.appendChild(tableWrapper);

            const buttonGroup = document.createElement('div');
            buttonGroup.className = 'button-group';
            payoutContainer.appendChild(buttonGroup);

            const copyBalancesButton = createButton('Copy Balances', 'action-button copy-button', () => {
                const text = attackers.filter(a => a.newBalance !== null).map(a => `${a.username} [${a.id}] New Balance: ${formatCurrency(a.newBalance)}`).join('\n');
                copyToClipboard(text, copyBalancesButton, 'Copy Balances');
            });

            const copyFactionEmailButton = createButton('Copy Faction Email', 'action-button copy-button', () => {
                const factionReportHTML = generateFactionWideReportHTML(attackers, chainReport, BCR, totalFinalPayout);
                copyToClipboard(factionReportHTML.replace(/\n\s*/g, ' ').trim(), copyFactionEmailButton, 'Copy Faction Email');
            });
            copyFactionEmailButton.style.backgroundColor = '#28a745';

            const markPaidButton = createButton('Mark as Paid', 'action-button mark-paid-button', async () => {
                await GM_set(storageKey, true);
                document.getElementById('paid-out-warning').style.display = 'block';
                markPaidButton.disabled = true;
                markPaidButton.textContent = 'Paid!';
            });
            markPaidButton.disabled = isPaid;

            const closeButton = createButton('Close', 'action-button close-button', () => payoutContainer.remove());
            buttonGroup.append(copyBalancesButton, copyFactionEmailButton, markPaidButton, closeButton);

            const individualReportContainer = document.createElement('div');
            individualReportContainer.id = 'individual-report-container';
            individualReportContainer.style.display = 'none';
            payoutContainer.appendChild(individualReportContainer);

            table.addEventListener('click', (event) => {
                const target = event.target;
                if (target.classList.contains('clickable-name')) {
                    const attackerId = parseInt(target.dataset.id, 10);
                    const attacker = attackers.find(a => a.id === attackerId);
                    generateIndividualReport(attacker, individualReportContainer, chainReport);
                } else {
                    handleTableCellClick(event);
                }
            });

            payoutContainer.scrollIntoView({ behavior: 'smooth' });
        }

        function handleTableCellClick(event) {
            const cell = event.target.closest('td');
            if (!cell || cell.closest('thead') || cell.closest('tfoot')) return;
            let valueToCopy = cell.textContent.trim();
            if (!valueToCopy || valueToCopy === 'N/A') return;
            if (valueToCopy.startsWith('$')) {
                valueToCopy = valueToCopy.replace(/[$,]/g, '');
            }
            navigator.clipboard.writeText(valueToCopy).then(() => {
                cell.classList.add('cell-copied');
                setTimeout(() => cell.classList.remove('cell-copied'), 1500);
            }).catch(err => {
                console.error('Torn Payout Script: Could not copy cell value.', err);
            });
        }

        function generateIndividualReport(attacker, container, chainReport) {
            container.style.display = 'block';
            container.innerHTML = `<h4>HTML Payout Report for ${attacker.username} [${attacker.id}]</h4>`;
            const isNewBalanceRowPresent = attacker.newBalance !== null;
            const tdBorderStyle = "1px solid #444";
            const tdNoBorderStyle = "none";
            const xanaxRowBottomBorder = isNewBalanceRowPresent ? tdBorderStyle : tdNoBorderStyle;
            const thStyleLeft = "background-color: #4CAF50; color: white; padding: 12px 15px; text-align: left; border-bottom: 1px solid #3c8e40; font-size: 1em;";
            const thStyleRight = "background-color: #4CAF50; color: white; padding: 12px 15px; text-align: right; border-bottom: 1px solid #3c8e40; font-size: 1em;";
            const tdStyleLeft = "padding: 10px 15px; text-align: left; color: #e0e0e0;";
            const tdStyleRight = "padding: 10px 15px; text-align: right; color: #e0e0e0;";
            const totalPayoutColor = "#76ff03";
            const reportHTML = `<div style="font-family: Arial, Helvetica, sans-serif; background-color: #1a1a1a; color: #e0e0e0; padding: 20px; max-width: 600px; margin: 0 auto; border-radius: 8px; border: 1px solid #333;"><h3 style="color: #e0e0e0; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; margin-top: 0; margin-bottom: 20px; font-size: 1.4em; text-align: center;">Chain Payout Report</h3><p style="color: #e0e0e0; font-size: 1.1em; margin-bottom: 10px; line-height: 1.6;">Hi ${attacker.username},</p><p style="color: #cccccc; line-height: 1.6; margin-bottom: 15px;">Thank you for your contribution to chain ID: ${chainReport.id}. Here's your payout summary:</p><table style="width: 100%; border-collapse: collapse; background-color: #2c2c2c; color: #e0e0e0; margin-bottom: 20px; border: 1px solid #444; border-radius: 5px; overflow: hidden;"><thead><tr><th style="${thStyleLeft}">Description</th><th style="${thStyleRight}">Value</th></tr></thead><tbody><tr style="background-color: #2c2c2c;"><td style="${tdStyleLeft} border-bottom: ${tdBorderStyle};">Total Attacks</td><td style="${tdStyleRight} border-bottom: ${tdBorderStyle};">${attacker.attacks.total}</td></tr><tr style="background-color: #333333;"><td style="${tdStyleLeft} border-bottom: ${tdBorderStyle};">Total Respect Gained</td><td style="${tdStyleRight} border-bottom: ${tdBorderStyle};">${attacker.respect.total.toFixed(2)}</td></tr><tr style="background-color: #2c2c2c;"><td style="${tdStyleLeft} border-bottom: ${tdBorderStyle};">Performance Payout (ICS)</td><td style="${tdStyleRight} border-bottom: ${tdBorderStyle};">${formatCurrency(attacker.performancePayout)}</td></tr><tr style="background-color: #333333;"><td style="${tdStyleLeft} border-bottom: ${tdBorderStyle};">Xanax Reimbursement</td><td style="${tdStyleRight} border-bottom: ${tdBorderStyle};">${formatCurrency(attacker.xanaxReimbursement)}</td></tr><tr style="background-color: #2c2c2c; font-weight: bold;"><td style="${tdStyleLeft} border-bottom: ${xanaxRowBottomBorder};">Total Payout</td><td style="${tdStyleRight} border-bottom: ${xanaxRowBottomBorder}; color: ${totalPayoutColor};">${formatCurrency(attacker.payout)}</td></tr>${isNewBalanceRowPresent ? `<tr style="background-color: #333333;"><td style="${tdStyleLeft} border-bottom: ${tdNoBorderStyle};">New Faction Balance</td><td style="${tdStyleRight} border-bottom: ${tdNoBorderStyle};">${formatCurrency(attacker.newBalance)}</td></tr>` : ''}</tbody></table><p style="color: #cccccc; line-height: 1.6; margin-bottom: 15px;">The funds have been ${isNewBalanceRowPresent ? 'added to your faction balance' : 'sent to you'}. Keep up the great work!</p><p style="font-size: 0.85em; color: #999999; text-align: center; margin-top: 30px; margin-bottom: 0;"><em>This is an automated message generated by the Faction Payout Script.</em></p></div>`;
            container.insertAdjacentHTML('beforeend', reportHTML);
            const copyHtmlButton = createButton('Copy HTML for Mail', 'action-button copy-button', () => {
                copyToClipboard(reportHTML.replace(/\n\s*/g, ' ').trim(), copyHtmlButton, 'Copy HTML for Mail');
            });
            copyHtmlButton.style.marginTop = '10px';
            container.appendChild(copyHtmlButton);
            container.scrollIntoView({ behavior: 'smooth', block: 'end' });
        }

        function generateFactionWideReportHTML(attackers, chainReport, bcr, totalChainFinalPayout) {
            const thStyleLeft = "background-color: #4CAF50; color: white; padding: 10px 12px; text-align: left; border-bottom: 1px solid #3c8e40; font-size: 0.95em;";
            const thStyleRight = "background-color: #4CAF50; color: white; padding: 10px 12px; text-align: right; border-bottom: 1px solid #3c8e40; font-size: 0.95em;";
            const tdStyleLeft = "padding: 8px 12px; text-align: left; color: #e0e0e0; border-bottom: 1px solid #444;";
            const tdStyleRight = "padding: 8px 12px; text-align: right; color: #e0e0e0; border-bottom: 1px solid #444;";
            const lastDataRowTdStyleLeft = "padding: 8px 12px; text-align: left; color: #e0e0e0; border-bottom: none;";
            const lastDataRowTdStyleRight = "padding: 8px 12px; text-align: right; color: #e0e0e0; border-bottom: none;";
            let attackersHtml = '';
            const numAttackers = attackers.length;
            if (numAttackers === 0) {
                attackersHtml = `<tr><td colspan="4" style="${tdStyleLeft} text-align: center; border-bottom: none;">No participants in this chain.</td></tr>`;
            } else {
                attackers.forEach((attacker, index) => {
                    const rowBackground = index % 2 === 0 ? '#2c2c2c' : '#333333';
                    attackersHtml += `<tr style="background-color: ${rowBackground};"><td style="${index === numAttackers - 1 ? lastDataRowTdStyleLeft : tdStyleLeft}">${attacker.username} [${attacker.id}]</td><td style="${index === numAttackers - 1 ? lastDataRowTdStyleRight : tdStyleRight}">${attacker.respect.total.toFixed(2)}</td><td style="${index === numAttackers - 1 ? lastDataRowTdStyleRight : tdStyleRight}">${attacker.attacks.total}</td><td style="${index === numAttackers - 1 ? lastDataRowTdStyleRight : tdStyleRight}; color: #76ff03;">${formatCurrency(attacker.payout)}</td></tr>`;
                });
            }
            const reportHTML = `<div style="font-family: Arial, Helvetica, sans-serif; background-color: #1a1a1a; color: #e0e0e0; padding: 20px; max-width: 700px; margin: 0 auto; border-radius: 8px; border: 1px solid #333;"><h3 style="color: #e0e0e0; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; margin-top: 0; margin-bottom: 20px; font-size: 1.4em; text-align: center;">Faction Chain Payout Summary</h3><p style="color: #cccccc; line-height: 1.6; margin-bottom: 15px;">Here's a summary of the payouts for our recent chain (ID: ${chainReport.id}), based on a BCR of ${formatCurrency(bcr)} and Xanax reimbursement of ${formatCurrency(XANAX_COST)} per ${ATTACKS_PER_XANAX} attacks.</p><table style="width: 100%; border-collapse: collapse; background-color: #2c2c2c; color: #e0e0e0; margin-bottom: 20px; border: 1px solid #444; border-radius: 5px; overflow: hidden;"><thead><tr><th style="${thStyleLeft}">Player Name [ID]</th><th style="${thStyleRight}">Respect</th><th style="${thStyleRight}">Attacks</th><th style="${thStyleRight}">Final Payout</th></tr></thead><tbody>${attackersHtml}${(totalChainFinalPayout > 0 && numAttackers > 0) ? `<tr style="background-color: #4CAF50; color: white; font-weight: bold;"><td colspan="3" style="padding: 10px 12px; text-align: left; border-top: 1px solid #3c8e40;"><strong>Total Payout Distributed</strong></td><td style="padding: 10px 12px; text-align: right; border-top: 1px solid #3c8e40;"><strong>${formatCurrency(totalChainFinalPayout)}</strong></td></tr>` : ''}</tbody></table><p style="color: #cccccc; line-height: 1.6;">Well done to all participants!</p><p style="font-size: 0.85em; color: #999999; text-align: center; margin-top: 30px; margin-bottom: 0;"><em>This is an automated message generated by the Faction Payout Script.</em></p></div>`;
            return reportHTML;
        }

        const formatCurrency = (num) => '$' + Math.round(num).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
        const createButton = (text, className, onClick) => {
            const btn = document.createElement('button');
            btn.textContent = text;
            btn.className = className;
            btn.addEventListener('click', onClick);
            return btn;
        };
        const copyToClipboard = (text, button, originalText) => {
            navigator.clipboard.writeText(text).then(() => {
                button.textContent = 'Copied!';
                setTimeout(() => { button.textContent = originalText; }, 2000);
            }).catch(err => {
                console.error('Failed to copy:', err);
                alert('Could not copy to clipboard.');
            });
        };
    }
})();