TreeDibsMapper

Dibs, Faction-wide notes, and war management systems for Torn (PC AND TornPDA Support)

目前為 2025-08-25 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name          TreeDibsMapper
// @namespace     http://tampermonkey.net/
// @version       3.5.0
// @description   Dibs, Faction-wide notes, and war management systems for Torn (PC AND TornPDA Support)
// @author        TreeMapper [3573576]
// @match         https://www.torn.com/loader.php?sid=attack&user2ID=*
// @match         https://www.torn.com/factions.php*
// @grant         GM_xmlhttpRequest
// @grant         GM_getValue
// @grant         GM_setValue
// @grant         GM_deleteValue
// @grant         GM_registerMenuCommand
// @grant         GM_listValues
// @grant         GM_addStyle
// @connect       api.torn.com
// @connect       us-central1-tornuserstracker.cloudfunctions.net
// @connect       apiget-codod64xdq-uc.a.run.app
// @connect       apipost-codod64xdq-uc.a.run.app
// ==/UserScript==

/*
    Documentation: Torn Faction Dibs and War Management Userscript

    *** FOR MY CARTELIANS @ NEON CARTEL ONLY ***
    https://www.torn.com/factions.php?step=profile&ID=41419

    This userscript provides a comprehensive dibs, war management, and user notes system
    for the game Torn. Authentication has been simplified to directly use the Torn API Key
    provided by the user for server-side verification.
    If you have any issues, send me a console log on discord.

    Features:
    - Centralized Dibs System
    - Stores and manages dibs data in Firestore via Firebase Functions
    - Records attacker, opponent, and timestamp for dibs
    - Automatically removes dibs if attacker inactive for 5 minutes (server-side check)
    - Prevents multiple users from dibbing same target simultaneously (server-side)
    - Notifies users when their dibs are removed (client-side)
    - Show retaliation opportunities

    - War Management
    - Displays current war type - can be set by Leader, Co-leader, Capo, Sicario, or Diablo
    - Med Deal tracking to indicate special agreements
    - One dibs & one med deal per user (if Termed War)
    - Adding new dibs/med deal deactivates old one

    - User Notes
    - Shared across faction
    - Persist and can be added to anyone in faction from faction page

    - UI Integration
    - Adds "Dibs" and "Notes" columns to ranked war pages
    - Toggle buttons for 'lvl', 'member-icons', 'position', and 'days' columns
    - Dibs removal notifications via toast messages
    - Attack page integration with Dibs, Med Deal, Notes and Assist buttons
    - Manual Assist message required in faction chat per Torn rules

    - Performance
    - Inactivity-based API call reduction
    - Dynamic API frequency based on user activity and window focus

    Settings (stored in localStorage):
    - columnVisibility: Toggle visibility of various columns in the UI
    - adminFunctionality: Enable/disable admin features for authorized users
    - toggle timers for inactivity, chain, and opponent status

    API:
    - **Simplified Authentication:** Relies on sending the Torn API Key with each function call for server-side verification.
    - **A Limited Key is Required:** This script now requires a Limited API Key for calls to the torn attacks API.
    - **Torn PDA Integration:** Now correctly integrates with PDA by using `###PDA-APIKEY###` placeholder and relying on PDA's `GM_` function emulation.

    Torn API Terms of Service Compliance: This script is designed to comply with Torn's API Terms of Service. Users are encouraged to review these terms to ensure their usage aligns with Torn's policies.
    Data Storage: Cached Locally, API Keys stored on Firebase Firestore collection only accessible by Author
    Data Sharing: Faction-wide sharing of dibs, war data (attack metrics), user notes, and organized crimes data
    Purpose of Use: Competitive Advantage in ranked wars
    Key Storage: API Keys are stored securely in Firebase Firestore and are only accessible by the script author.
    Key Access Level: Limited (requires factions (attacks, members, basic), users (attacks, basic))


    TODO:
    - Make sure unauthorized attacks are actually tracked on the db.
    - Always need css tweaking. Any experts experts welcome.

    Data Flow:
    Torn Page UI <-> Userscript (Tampermonkey/PDA) <-> Firebase Cloud Functions (HTTPS API) <-> Firestore Database

    PDA:
    - This script relies on PDA's emulation of standard `GM_` functions and its `###PDA-APIKEY###` placeholder.

*/

(function() {
    'use strict';

    //======================================================================
    // 1. CONFIGURATION
    //======================================================================
    const config = {
    VERSION: '3.5.0',
        PDA_API_KEY_PLACEHOLDER: '###PDA-APIKEY###',
        API_GET_URL: 'https://apiget-codod64xdq-uc.a.run.app',
        API_POST_URL: 'https://apipost-codod64xdq-uc.a.run.app',
        GREASYFORK: {
            scriptId: '540873',
            pageUrl: 'https://greasyfork.org/en/scripts/540873',
            updateMetaUrl: 'https://update.greasyfork.org/scripts/540873/TreeDibsMapper.meta.js',
            downloadUrl: 'https://update.greasyfork.org/scripts/540873/TreeDibsMapper.user.js'
        },
        // Throttles to coalesce rapid events (PDA can be chatty on load)
        MIN_GLOBAL_FETCH_INTERVAL_MS: 2000,
        MIN_RETALS_FETCH_INTERVAL_MS: 1500,
        REFRESH_INTERVAL_ACTIVE_MS: 10000,
        REFRESH_INTERVAL_INACTIVE_MS: 60000,
        ACTIVITY_TIMEOUT_MS: 30000,
        DEFAULT_COLUMN_VISIBILITY: {
            rankedWar: {
                lvl: true,
                factionIcon: true
            },
            membersList: {
                lvl: true,
                memberIcons: true,
                position: true,
                days: true,
                factionIcon: true,
                dibsDeals: true,
                notes: true
            }
        },
    DEFAULT_SETTINGS: { showAllRetaliations: false, chainTimerEnabled: true, inactivityTimerEnabled: true, opponentStatusTimerEnabled: true, apiUsageCounterEnabled: true },
    ALLOWED_POSITIONS_FOR_WAR_CONTROLS: ["Leader", "Co-leader", "Capo", "Sicario", "Diablo"], // legacy fallback only; server-driven adminRoles preferred
        CSS: {
            colors: {
                success: '#4CAF50', error: '#f44336', warning: '#ff9800', info: '#2196F3',
                dibsSuccess: '#8e261f', dibsSuccessHover: '#7a1e1a', dibsOther: '#105d22',
                dibsOtherHover: '#0b4e1f', dibsInactive: '#854e00cb', dibsInactiveHover: '#6f3f00',
                noteInactive: '#0032b1', noteInactiveHover: '#00209c', noteActive: '#670295',
                noteActiveHover: '#634466', medDealInactive: '#970167cb', medDealInactiveHover: '#7B1FA2',
                medDealSet: '#b600ad', medDealSetHover: '#9C27B0', medDealMine: '#105d22',
                medDealMineHover: '#0b4e1f', assistButton: '#40004bff', assistButtonHover: '#35003aff',
                modalBg: '#1a1a1a', modalBorder: '#333', buttonBg: '#2c2c2c', mainColor: '#344556'
            }
        }
    };

    //======================================================================
    // 2. STORAGE & UTILITIES
    //======================================================================
    const storage = {
        get: (key, defaultValue) => {
            try {
                const value = localStorage.getItem(`tdm_${key}`);
                return value !== null ? JSON.parse(value) : defaultValue;
            } catch (e) {
                console.error(`[TDM] Error getting setting ${key}:`, e);
                return defaultValue;
            }
        },
        set: (key, value) => {
            try {
                localStorage.setItem(`tdm_${key}`, JSON.stringify(value));
                return true;
            } catch (e) {
                if (e.name === 'QuotaExceededError') {
                    ui.showMessageBox('Storage quota exceeded. Some settings may not be saved.', 'error');
                } else {
                    console.error(`[TDM] Error setting ${key}:`, e);
                }
                return false;
            }
        },
        updateStateAndStorage: (key, value) => {
            state[key] = value;
            storage.set(key, value);
        }
    };

    const utils = {
        debounce: (func, delay) => {
            let timeout;
            return function(...args) {
                const context = this;
                clearTimeout(timeout);
                timeout = setTimeout(() => func.apply(context, args), delay);
            };
        },
        incrementApiCalls: (n = 1) => {
            try {
                state.session.apiCalls = (state.session.apiCalls || 0) + (Number(n) || 0);
                if (ui && typeof ui.updateApiUsageBadge === 'function') ui.updateApiUsageBadge();
            } catch (_) { /* noop */ }
        },
        createElement: (tag, attributes = {}, children = []) => {
            const element = document.createElement(tag);
            Object.entries(attributes).forEach(([key, value]) => {
                if (key === 'style' && typeof value === 'object') Object.assign(element.style, value);
                else if (key === 'className' || key === 'class') element.className = value;
                else if (key === 'innerHTML') element.innerHTML = value;
                else if (key === 'textContent') element.textContent = value;
                else if (key === 'onclick') element.addEventListener('click', value);
                else if (key.startsWith('on') && typeof value === 'function') element.addEventListener(key.substring(2).toLowerCase(), value);
                else if (key === 'dataset' && typeof value === 'object') Object.entries(value).forEach(([dataKey, dataValue]) => element.dataset[dataKey] = dataValue);
                else element.setAttribute(key, value);
            });
            children.forEach(child => {
                if (typeof child === 'string') element.appendChild(document.createTextNode(child));
                else if (child instanceof Node) element.appendChild(child);
            });
            return element;
        },
        compareVersions: (a, b) => {
            // Returns -1 if a<b, 0 if equal, 1 if a>b
            const pa = String(a).split('.').map(n => parseInt(n, 10) || 0);
            const pb = String(b).split('.').map(n => parseInt(n, 10) || 0);
            const len = Math.max(pa.length, pb.length);
            for (let i = 0; i < len; i++) {
                const ai = pa[i] || 0; const bi = pb[i] || 0;
                if (ai < bi) return -1;
                if (ai > bi) return 1;
            }
            return 0;
        },
        httpGetText: (url) => {
            // Prefer GM/pda bridge to avoid CORS; fallback to fetch
            return new Promise((resolve) => {
                try {
                    if (state?.gm?.rD_xmlhttpRequest) {
                        state.gm.rD_xmlhttpRequest({
                            method: 'GET', url,
                            onload: r => resolve(typeof r.responseText === 'string' ? r.responseText : ''),
                            onerror: () => resolve('')
                        });
                        return;
                    }
                } catch (_) { /* ignore and try fetch */ }
                // Fallback to fetch
                fetch(url).then(res => res.text()).then(txt => resolve(txt)).catch(() => resolve(''));
            });
        },
        perf: {
            timers: {},
            start: function(name) {
                this.timers[name] = performance.now();
            },
            stop: function(name) {
                if (this.timers[name]) {
                    const duration = performance.now() - this.timers[name];
                    console.log(`[TDM Perf] ${name} took ${duration.toFixed(2)} ms`);
                    delete this.timers[name];
                }
            }
        },
        isCollectionChanged: (clientTimestamps, masterTimestamps, collectionKey) => {
            const clientTs = clientTimestamps?.[collectionKey];
            const masterTs = masterTimestamps?.[collectionKey];
            if (!masterTs) {
                console.log(`[TDM] No master timestamp for ${collectionKey} Default True`);
                return true; // If no master, always fetch
            }
            if (!clientTs) {
                console.log(`[TDM] No client timestamp for ${collectionKey} Default True`);
                return true; // If no client, always fetch
            }
            // Compare Firestore Timestamp objects
            const masterMillis = masterTs._seconds ? masterTs._seconds * 1000 : (masterTs.toMillis ? masterTs.toMillis() : 0);
            const clientMillis = clientTs._seconds ? clientTs._seconds * 1000 : (clientTs.toMillis ? clientTs.toMillis() : 0);
            const isChanged = masterMillis > clientMillis;
            if (isChanged) {
                console.log(`[TDM][Collection][${collectionKey}]: `, {changed: isChanged, master: masterMillis, client: clientMillis});
            }
            return isChanged;
        },
        getVisibleOpponentIds: () => {
            const ids = new Set();
            try {
                // Ranked war tables
                document.querySelectorAll('.tab-menu-cont .members-list > li a[href*="profiles.php?XID="]').forEach(a => {
                    const m = a.href.match(/XID=(\d+)/);
                    if (m) ids.add(m[1]);
                });
                // Faction page list
                document.querySelectorAll('.f-war-list .table-body a[href*="profiles.php?XID="]').forEach(a => {
                    const m = a.href.match(/XID=(\d+)/);
                    if (m) ids.add(m[1]);
                });
                // Attack page current opponent
                const attackId = new URLSearchParams(window.location.search).get('user2ID');
                if (attackId) ids.add(String(attackId));
            } catch (_) { /* ignore */ }
            return Array.from(ids);
        },
        getClientNoteTimestamps: () => {
            const map = {};
            try {
                for (const [id, note] of Object.entries(state.userNotes || {})) {
                    const ts = note?.lastEdited?._seconds ? note.lastEdited._seconds * 1000 : (note?.lastEdited?.toMillis ? note.lastEdited.toMillis() : (note?.lastEdited ? new Date(note.lastEdited).getTime() : 0));
                    map[id] = ts || 0;
                }
            } catch (_) { /* ignore */ }
            return map;
        },
        canonicalizeStatus: (stateStr, descriptionStr = '') => {
            try {
                const s = String(stateStr || '').toLowerCase();
                const d = String(descriptionStr || '').toLowerCase();
                if (s.includes('hospital')) return 'Hospital';
                if (s.includes('jail')) return 'Jail';
                if (s.includes('travel')) return 'Travel'; // covers 'Traveling'
                if (s.includes('abroad') || d.includes('abroad')) return 'Abroad';
                if (s === 'okay' || d.includes('okay')) return 'Okay';
                // Fallbacks for common variants
                if (d.includes('hospital')) return 'Hospital';
                if (d.includes('jail')) return 'Jail';
                if (d.includes('travel')) return 'Travel';
                return 'Okay';
            } catch (_) { return 'Okay'; }
        },
        getDibsStyleOptions: () => {
            const fs = (state.script && state.script.factionSettings) || {};
            const dibs = (fs.options && fs.options.dibsStyle) || {};
            const defaultStatuses = { Okay: true, Hospital: true, Travel: false, Abroad: false, Jail: false };
            return {
                keepTillInactive: dibs.keepTillInactive !== false,
                mustRedibAfterSuccess: !!dibs.mustRedibAfterSuccess,
                inactivityTimeoutSeconds: parseInt(dibs.inactivityTimeoutSeconds || 300),
                // New: if > 0, only allow dibbing Hospital opponents when release time < N minutes
                maxHospitalReleaseMinutes: Number.isFinite(Number(dibs.maxHospitalReleaseMinutes)) ? Number(dibs.maxHospitalReleaseMinutes) : 0,
                // Opponent status allowance
                allowStatuses: { ...defaultStatuses, ...(dibs.allowStatuses || {}) },
                // User status allowance
                allowedUserStatuses: { ...defaultStatuses, ...(dibs.allowedUserStatuses || {}) },
                // Opponent travel removal
                removeOnFly: !!dibs.removeOnFly,
                // User travel removal
                removeWhenUserTravels: !!dibs.removeWhenUserTravels,
            };
        },
        getUserStatus: async (userId /* string|number|null/undefined = self */) => {
            // Cached Torn user status fetcher with small TTL
            const id = userId ? String(userId) : String(state.user.tornId || 'self');
            const now = Date.now();
            const cache = state.session.userStatusCache || (state.session.userStatusCache = {});
            const cached = cache[id];
            if (cached && (now - cached.fetchedAtMs < 10000)) return cached; // 10s TTL
            try {
                const user = await api.getTornUser(state.user.actualTornApiKey, userId ? id : null);
                const canon = utils.canonicalizeStatus(user?.status?.state, user?.status?.description);
                const until = Number(user?.status?.until || 0);
                const packed = { raw: user?.status || {}, canonical: canon, until, fetchedAtMs: now };
                cache[id] = packed;
                return packed;
            } catch (e) {
                // Fallback to last known factionMembers/self data if any
                let canon = 'Okay', until = 0;
                if (!userId) {
                    const selfMember = (state.factionMembers || []).find(m => String(m.id) === String(state.user.tornId));
                    if (selfMember?.status) {
                        canon = utils.canonicalizeStatus(selfMember.status?.state, selfMember.status?.description);
                    }
                }
                const packed = { raw: {}, canonical: canon, until, fetchedAtMs: now };
                cache[id] = packed;
                return packed;
            }
        },
        getMyCanonicalStatus: () => {
            // Best-effort local status (no network)
            try {
                const selfMember = (state.factionMembers || []).find(m => String(m.id) === String(state.user.tornId));
                if (selfMember?.status) return utils.canonicalizeStatus(selfMember.status?.state, selfMember.status?.description);
                const obj = state.user?.tornUserObject;
                if (obj?.status) return utils.canonicalizeStatus(obj.status?.state, obj.status?.description);
            } catch (_) {}
            return 'Okay';
        }
    };
    //======================================================================
    // 3. STATE MANAGEMENT
    //======================================================================
    // Initialize state keys from localStorage or use default values
    const state = {
        dibsData: storage.get('dibsData', []),
        warData: storage.get('warData', { warType: 'War Type Not Set' }),
        rankWars: storage.get('rankWars', []),
        lastRankWar: storage.get('lastRankWar', { id: 1, start: 0, end: 0, target: 42069, winner: null, factions: [{ id: 41419, name: "Neon Cartel", score: 7620, chain: 666 }] }),
        lastOpponentFactionId: storage.get('lastOpponentFactionId', 0),
        lastOpponentFactionName: storage.get('lastOpponentFactionName', 'Not Pulled'),
        opponentStatuses: storage.get('opponentStatuses', {}),
        userNotes: storage.get('userNotes', {}),
        factionMembers: storage.get('factionMembers', []),
        factionPull: storage.get('factionPull', null),
        dibsNotifications: storage.get('dibsNotifications', []),
        unauthorizedAttacks: storage.get('unauthorizedAttacks', []),
        retaliationOpportunities: storage.get('retaliationOpportunities', {}),
        dataTimestamps: storage.get('dataTimestamps', {}), // --- Timestamp-based polling ---
        user: storage.get('user', { tornId: null, tornUsername: '', tornUserObject: null, actualTornApiKey: null, actualTornApiKeyAccess: 0, hasReachedScoreCap: false, factionId: null }),
        page: { url: new URL(window.location.href), isFactionProfilePage: false, isMyFactionPrivatePage: false, isMyFactionProfilePage: false, isMyFactionYourInfoTab: false, isRankedWarPage: false, isFactionPage: false, isMyFactionPage: false, isAttackPage: false },
        dom: { factionListContainer: null, customControlsContainer: null, rankwarContainer: null, rankwarmembersWrap: null, rankwarfactionTables: null, rankBox: null },
        script: { currentUserPosition: null, canAdministerMedDeals: false, lastActivityTime: Date.now(), isWindowActive: true, currentRefreshInterval: config.REFRESH_INTERVAL_ACTIVE_MS, mainRefreshIntervalId: null, activityTimeoutId: null, mutationObserver: null, hasProcessedRankedWarTables: false, hasProcessedFactionList: false },
    ui: { retalNotificationActive: false, retalNotificationElement: null, retalTimerIntervals: [], noteModal: null, noteTextarea: null, currentNoteTornID: null, currentNoteTornUsername: null, currentNoteButtonElement: null, setterModal: null, setterList: null, setterSearchInput: null, currentOpponentId: null, currentOpponentName: null, currentButtonElement: null, currentSetterType: null, unauthorizedAttacksModal: null, currentWarAttacksModal: null, chainTimerEl: null, chainTimerIntervalId: null, chainFallback: { lastFetch: 0, timeoutEpoch: 0 }, inactivityTimerEl: null, inactivityTimerIntervalId: null, opponentStatusEl: null, opponentStatusIntervalId: null, opponentStatusCache: { lastFetch: 0, untilEpoch: 0, text: '', opponentId: null } },
        gm: { rD_xmlhttpRequest: null, rD_setValue: null, rD_getValue: null, rD_registerMenuCommand: null, rD_listValues: null, rD_getApiKey: null, rD_addStyle: null },
        session: { apiCalls: 0, userStatusCache: {}, lastEnforcementMs: 0 }
    };


    //======================================================================
    // 4. API MODULE
    //======================================================================
    const api = {
    _call: (method, url, action, params = {}) => {
            if (!state.user.tornId || !state.user.actualTornApiKey) {
                return Promise.reject(new Error(`Authentication details missing for Firebase ${method}.`));
            }
            // Pass config.VERSION to backend
            const defaultFaction = state?.user?.factionId ? { factionId: state.user.factionId } : {};
            const payload = { action: action, tornId: state.user.tornId, tornApiKey: state.user.actualTornApiKey, version: config.VERSION, clientTimestamps: state.dataTimestamps, ...defaultFaction, ...params };
            const requestBody = { data: payload };

            return new Promise((resolve, reject) => {
                state.gm.rD_xmlhttpRequest({
                    method: 'POST',
                    url: url,
                    headers: { 'Content-Type': 'application/json' },
                    data: JSON.stringify(requestBody),
                    onload: function(response) {
                        const raw = typeof response?.responseText === 'string' ? response.responseText : '';
                        // If HTTP error, still try to parse error payload then reject
                        const isHttpError = typeof response?.status === 'number' && response.status >= 400;
                        if (!raw || raw.trim() === '') {
                            return reject(new Error(`Empty or invalid response from server (${method}).`));
                        }
                        try {
                            const payload = JSON.parse(raw);
                            // If backend included meta with userKeyApiCalls, accumulate it
                            try {
                                const metaCalls = payload?.result?.meta?.userKeyApiCalls ?? payload?.meta?.userKeyApiCalls;
                                // console.log('[TDM][API] Backend reported userKeyApiCalls:', metaCalls);
                                if (typeof metaCalls === 'number') utils.incrementApiCalls(Number(metaCalls) || 0);
                            } catch (_) { /* ignore */ }
                            // Normalize common shapes:
                            // 1) { result: { status: 'success', data } }
                            if (payload?.result?.status === 'success' && 'data' in payload.result) {
                                return resolve(payload.result.data);
                            }
                            // 2) { status: 'success', data }
                            if (payload?.status === 'success' && 'data' in payload) {
                                return resolve(payload.data);
                            }
                            // 3) Firebase error shapes: { error: { message, status, details } } or { result: { error: { ... } } }
                            const fbError = payload?.error || payload?.result?.error || payload?.data?.error;
                            if (fbError) {
                                const err = new Error(fbError.message || 'Firebase error');
                                // Flatten useful fields for handlers
                                if (fbError.status) err.code = fbError.status;
                                if (fbError.details && typeof fbError.details === 'object') {
                                    Object.assign(err, fbError.details);
                                }
                                return reject(err);
                            }
                            // 4) { result: { data } } without status
                            if (payload?.result && 'data' in payload.result) {
                                return resolve(payload.result.data);
                            }
                            // 5) Fallback to resolve normalized object
                            if (isHttpError) {
                                return reject(new Error(`Request failed (${response.status}): ${raw.slice(0, 200)}`));
                            }
                            return resolve(payload);
                        } catch (e) {
                            // Parsing failed; include a hint for easier debugging
                            return reject(new Error(`Failed to parse Firebase API response: ${e.message}`));
                        }
                    },
                    onerror: (error) => reject(new Error(`Firebase API request failed: Status ${error.status || 'Unknown'}`))
                });
            });
        },
        get: (action, params = {}) => api._call('GET', config.API_GET_URL, action, params),
        post: (action, data = {}) => api._call('POST', config.API_POST_URL, action, data),
        getGlobalData: (params = {}) => {
            // Use the dedicated backend endpoint for getGlobalData
            if (!state.user.tornId || !state.user.actualTornApiKey) {
                return Promise.reject(new Error('Authentication details missing for getGlobalData.'));
            }
            // --- Differential polling: send client timestamps ---
            const payload = {
                tornId: state.user.tornId,
                tornApiKey: state.user.actualTornApiKey,
                clientTimestamps: state.dataTimestamps, // Send persisted timestamps
                ...params
            };
            return new Promise((resolve, reject) => {
                state.gm.rD_xmlhttpRequest({
                    method: 'POST',
                    url: 'https://getglobaldata-codod64xdq-uc.a.run.app',
                    headers: { 'Content-Type': 'application/json' },
                    data: JSON.stringify({ data: payload }),
                    onload: function(response) {
                        if (!response || typeof response.responseText !== 'string' || response.responseText.trim() === '') {
                            return reject(new Error('Empty or invalid response from getGlobalData endpoint.'));
                        }
                        try {
                            const jsonResponse = JSON.parse(response.responseText);
                            if (jsonResponse.error) {
                                const error = new Error(jsonResponse.error.message || 'Unknown getGlobalData error');
                                if (jsonResponse.error.details) Object.assign(error, jsonResponse.error.details);
                                reject(error);
                            } else if (jsonResponse.result) {
                                // Session API usage counter (backend-reported, user-key only)
                                if (jsonResponse.result.apiUsage && typeof jsonResponse.result.apiUsage.userKeyApiCalls === 'number') {
                                    utils.incrementApiCalls(Number(jsonResponse.result.apiUsage.userKeyApiCalls) || 0);
                                }
                                // --- Persist masterTimestamps after each fetch ---
                                if (jsonResponse.result.masterTimestamps) {
                                    state.dataTimestamps = jsonResponse.result.masterTimestamps;
                                    storage.set('dataTimestamps', state.dataTimestamps);
                                }
                                // --- Update only changed collections in state ---
                                // --- Support full backend response structure ---
                                const firebaseCollections = [
                                    'dibsData',
                                    'userNotes',
                                    'opponentStatuses',
                                    'dibsNotifications',
                                    'unauthorizedAttacks',
                                    'rankedWarSummary'
                                ];
                                const tornApiCollections = [
                                    'rankWars',
                                    'warData',
                                    'retaliationOpportunities'
                                ];
                                const actionsCollections = [
                                    'attackerLastAction',
                                    'unauthorizedAttacks'
                                ];
                                // Update firebase collections
                                if (jsonResponse.result.firebase && typeof jsonResponse.result.firebase === 'object') {
                                    for (const key of firebaseCollections) {
                                        if (jsonResponse.result.firebase.hasOwnProperty(key) && jsonResponse.result.firebase[key] !== null && jsonResponse.result.firebase[key] !== undefined) {
                                            storage.updateStateAndStorage(key, jsonResponse.result.firebase[key]);
                                            // state[key] = jsonResponse.result.firebase[key];
                                        }
                                    }
                                }
                                // Update tornApi collections
                                if (jsonResponse.result.tornApi && typeof jsonResponse.result.tornApi === 'object') {
                                    for (const key of tornApiCollections) {
                                        if (Object.prototype.hasOwnProperty.call(jsonResponse.result.tornApi, key) && jsonResponse.result.tornApi[key] !== null && jsonResponse.result.tornApi[key] !== undefined) {
                                            storage.updateStateAndStorage(key, jsonResponse.result.tornApi[key]);
                                            // state[key] = jsonResponse.result.tornApi[key];
                                        }
                                    }
                                }
                                // Update actions collections
                                if (jsonResponse.result.actions && typeof jsonResponse.result.actions === 'object') {
                                    for (const key of actionsCollections) {
                                        if (jsonResponse.result.actions.hasOwnProperty(key) && jsonResponse.result.actions[key] !== null && jsonResponse.result.actions[key] !== undefined) {
                                            storage.updateStateAndStorage(key, jsonResponse.result.actions[key]);
                                            // state[key] = jsonResponse.result.actions[key];
                                        }
                                    }
                                }
                                resolve(jsonResponse.result);
                            } else {
                                resolve(jsonResponse);
                            }
                        } catch (e) {
                            reject(new Error('Failed to parse getGlobalData response: ' + e.message));
                        }
                    },
                    onerror: (error) => reject(new Error('getGlobalData request failed: Status ' + (error.status || 'Unknown')))
                });
            });
        },
        getTornUser: (apiKey, id = null) => {
            if (!apiKey) return Promise.reject(new Error("No API key provided"));
            const apiUrl = `https://api.torn.com/v2/user/${id ? id + '/' : ''}?key=${apiKey}&comment=TreeDibsMapper`;
            return new Promise((resolve, reject) => {
                state.gm.rD_xmlhttpRequest({
                    method: "GET",
                    url: apiUrl,
                    onload: res => {
                        try {
                            const data = JSON.parse(res.responseText);
                            if (data.error) reject(new Error(data.error.error));
                            else { utils.incrementApiCalls(1); resolve(data); }
                        } catch (e) { reject(new Error("Invalid JSON from Torn API")); }
                    },
                    onerror: () => reject(new Error("Torn API request failed"))
                });
            });
        },
    getTornFaction: async function(apiKey, selections = '') {
            try {
        const factionId = state?.user?.factionId;
        if (!factionId) return null;
        const url = `https://api.torn.com/v2/faction/${factionId}?selections=${selections}&key=${apiKey}&comment=TreeDibsMapper`;
                const response = await fetch(url);
                const data = await response.json();
                if (data.error) throw new Error(data.error.error);
                utils.incrementApiCalls(1);
                return data;
            } catch (error) {
                console.error('[TDM] Error fetching faction data:', error);
                return null;
            }
        },
        getKeyInfo:async function(apiKey) {
            try{
                const url = `https://api.torn.com/v2/key/info?key=${apiKey}&comment=TDMKey`;
                const response = await fetch(url);
                const data = await response.json();
                if (data.error) throw new Error(data.error.error);
                utils.incrementApiCalls(1);
                return data;
            } catch (error) {
                console.error('[TDM] Error fetching key info:', error);
                return null;
            }
        }
    };

    //======================================================================
    // 5. UI MODULE
    //======================================================================
    const ui = {
        updatePageContext: () => {
            state.page.url = new URL(window.location.href);
            state.dom.factionListContainer = document.querySelector('.f-war-list.members-list');
            state.dom.customControlsContainer = document.querySelector('.dibs-system-main-controls');
            state.dom.rankwarContainer = document.querySelector('div.desc-wrap.warDesc___qZfyO');
            if (state.dom.rankwarContainer) { state.dom.rankwarmembersWrap = state.dom.rankwarContainer.querySelector('.faction-war.membersWrap___NbYLx'); }
            state.dom.rankwarfactionTables = document.querySelectorAll('.tab-menu-cont');
            state.dom.rankBox = document.querySelector('.rankBox___OzP3D');

            state.page.isFactionProfilePage = state.page.url.href.includes(`factions.php?step=profile`);
            state.page.isMyFactionPrivatePage = state.page.url.href.includes('factions.php?step=your');
            state.page.isRankedWarPage = !!state.dom.rankwarContainer;
            state.page.isMyFactionYourInfoTab = state.page.url.hash.includes('tab=info') && state.page.isMyFactionPrivatePage;
            state.page.isFactionPage = state.page.url.href.includes(`factions.php`);
            const isMyFactionById = state.page.isFactionPage && state.user.factionId && state.page.url.searchParams.get('ID') === state.user.factionId;
            state.page.isMyFactionProfilePage = isMyFactionById && (state.page.url.searchParams.get('step') === 'your' || state.page.url.searchParams.get('step') === 'profile');
            state.page.isMyFactionPage = state.page.isMyFactionProfilePage || state.page.isMyFactionPrivatePage || (state.page.isRankedWarPage && state.factionPull && state.user.factionId && state.factionPull.ID?.toString() === state.user.factionId);
            state.page.isAttackPage = state.page.url.href.includes('loader.php?sid=attack&user2ID=');
        },

        updateAllPages: () => {
            // utils.perf.start('updateAllPages');
            ui.updatePageContext();
            if (state.page.isAttackPage) ui.injectAttackPageUI();
            if (state.page.isRankedWarPage) ui.updateRankedWarUI();
            if (state.dom.factionListContainer) ui.updateFactionPageUI(state.dom.factionListContainer);
            ui.updateRetalsButtonCount();
            ui.ensureChainTimer();
            ui.ensureInactivityTimer();
            ui.ensureOpponentStatus();
            ui.ensureApiUsageBadge();
            // utils.perf.stop('updateAllPages');
        },

        ensureChainTimer: () => {
            if (!storage.get('chainTimerEnabled', true)) { ui.removeChainTimer(); return; }
            // Find chat root
            const chatRoot = document.querySelector('.root___lv7vM');
            if (!chatRoot) return;
            // Prefer existing element by ID and remove duplicates
            const allChainEls = document.querySelectorAll('#tdm-chain-timer');
            if (allChainEls.length > 1) {
                for (let i = 1; i < allChainEls.length; i++) allChainEls[i].remove();
            }
            const existing = document.getElementById('tdm-chain-timer');
            if (existing && existing !== state.ui.chainTimerEl) {
                state.ui.chainTimerEl = existing;
            }
            if (state.ui.chainTimerEl && !document.body.contains(state.ui.chainTimerEl)) {
                state.ui.chainTimerEl = null;
            }
            if (!state.ui.chainTimerEl) {
                const el = utils.createElement('div', {
                    id: 'tdm-chain-timer',
                    className: 'tdm-text-halo',
                    style: { display: 'inline-flex', alignItems: 'center', gap: '6px', marginRight: '8px', color: '#fff', fontWeight: '600', fontSize: '12px', padding: '1px 1px' }
                });
                // Non-intrusive: prepend without shifting chat controls by using flex inline container
                chatRoot.insertBefore(el, chatRoot.firstChild);
                state.ui.chainTimerEl = el;
                ui.ensureApiUsageBadge();
            } else if (state.ui.chainTimerEl.parentNode !== chatRoot) {
                chatRoot.insertBefore(state.ui.chainTimerEl, chatRoot.firstChild);
            }
            // Start/refresh updater
            ui.startChainTimerUpdater();
        },
        removeChainTimer: () => {
            if (state.ui.chainTimerIntervalId) { clearInterval(state.ui.chainTimerIntervalId); state.ui.chainTimerIntervalId = null; }
            if (state.ui.chainTimerEl) { state.ui.chainTimerEl.remove(); state.ui.chainTimerEl = null; }
        },
        startChainTimerUpdater: () => {
            if (!state.ui.chainTimerEl) return;
            if (state.ui.chainTimerIntervalId) return; // already running

            const readDomTimer = () => {
                const a = document.querySelector('.chain-box-timeleft');
                const b = document.querySelector('.bar-timeleft___B9RGV');
                const txt = (a?.textContent || b?.textContent || '').trim();
                // Expect MM:SS; ignore if 00:00 or empty
                if (txt && /^\d{1,2}:\d{2}$/.test(txt) && txt !== '00:00') return txt;
                return '';
            };

            const setDisplay = (text) => {
                if (!state.ui.chainTimerEl) return;
                if (text) {
                    state.ui.chainTimerEl.style.display = 'inline-flex';
                    state.ui.chainTimerEl.textContent = `Chain: ${text}`;
                } else {
                    state.ui.chainTimerEl.style.display = 'none';
                }
            };

            const tick = async () => {
                // First preference: mirror existing DOM timer
                const domVal = readDomTimer();
                if (domVal) { setDisplay(domVal); return; }

                // Fallback: use chain bar value to decide whether we care
                const barVal = document.querySelector('.bar-value___uxnah')?.textContent?.trim() || '';
                const validBar = barVal && !barVal.includes('0 / 10');

                if (validBar) {
                    const nowMs = Date.now();
                    // Fetch only every 10s
                    if (nowMs - state.ui.chainFallback.lastFetch >= 10000) {
                        state.ui.chainFallback.lastFetch = nowMs;
                        try {
                            const url = `https://api.torn.com/v2/faction/${state.user.factionId}/chain?key=${state.user.actualTornApiKey}`;
                            const resp = await fetch(url);
                            const json = await resp.json();
                            const chain = json?.chain;
                            if (chain && Number(chain.current) > 0) {
                                // timeout is epoch seconds
                                state.ui.chainFallback.timeoutEpoch = Number(chain.timeout) || 0;
                                utils.incrementApiCalls(1);
                            } else {
                                state.ui.chainFallback.timeoutEpoch = 0;
                            }
                        } catch (_) { /* noop */ }
                    }

                    // With cached timeout, compute remaining
                    if (state.ui.chainFallback.timeoutEpoch > 0) {
                        const now = Math.floor(Date.now() / 1000);
                        const remaining = state.ui.chainFallback.timeoutEpoch - now;
                        if (remaining > 0) {
                            const mm = Math.floor(remaining / 60);
                            const ss = (remaining % 60).toString().padStart(2, '0');
                            setDisplay(`${mm}:${ss}`);
                            return;
                        }
                    }
                }

                // Nothing to show
                setDisplay('');
            };

            // Initial tick and interval
            tick();
            state.ui.chainTimerIntervalId = setInterval(tick, 1000);
        },

        ensureInactivityTimer: () => {
            if (!storage.get('inactivityTimerEnabled', true)) { ui.removeInactivityTimer(); return; }
            const chatRoot = document.querySelector('.root___lv7vM');
            if (!chatRoot) return;
            const allEls = document.querySelectorAll('#tdm-inactivity-timer');
            if (allEls.length > 1) {
                for (let i = 1; i < allEls.length; i++) allEls[i].remove();
            }
            const existing = document.getElementById('tdm-inactivity-timer');
            if (existing && existing !== state.ui.inactivityTimerEl) {
                state.ui.inactivityTimerEl = existing;
            }
            if (state.ui.inactivityTimerEl && !document.body.contains(state.ui.inactivityTimerEl)) {
                state.ui.inactivityTimerEl = null;
            }
            if (!state.ui.inactivityTimerEl) {
                const el = utils.createElement('div', {
                    id: 'tdm-inactivity-timer',
                    className: 'tdm-text-halo',
                    style: { display: 'inline-flex', alignItems: 'center', gap: '2px', marginRight: '8px', marginLeft: '2px', color: '#ffeb3b', fontWeight: '600', fontSize: '12px', padding: '1px 1px' }
                });
                chatRoot.insertBefore(el, chatRoot.firstChild?.nextSibling || chatRoot.firstChild);
                state.ui.inactivityTimerEl = el;
                ui.ensureApiUsageBadge();
            } else if (state.ui.inactivityTimerEl.parentNode !== chatRoot) {
                chatRoot.insertBefore(state.ui.inactivityTimerEl, chatRoot.firstChild?.nextSibling || chatRoot.firstChild);
            }
            ui.startInactivityUpdater();
        },
        removeInactivityTimer: () => {
            if (state.ui.inactivityTimerIntervalId) { clearInterval(state.ui.inactivityTimerIntervalId); state.ui.inactivityTimerIntervalId = null; }
            if (state.ui.inactivityTimerEl) { state.ui.inactivityTimerEl.remove(); state.ui.inactivityTimerEl = null; }
        },
        startInactivityUpdater: () => {
            if (!state.ui.inactivityTimerEl) return;
            if (state.ui.inactivityTimerIntervalId) return;
            const tick = () => {
                const ms = Date.now() - (state.script.lastActivityTime || Date.now());
                const totalSec = Math.floor(ms / 1000);
                const hh = Math.floor(totalSec / 3600);
                const mm = Math.floor((totalSec % 3600) / 60);
                const ss = totalSec % 60;
                const pad = (n) => String(n).padStart(2, '0');
                const txt = hh > 0 ? `${pad(hh)}:${pad(mm)}:${pad(ss)}` : `${pad(mm)}:${pad(ss)}`;
                // set color orange after 4 minutes, red after 5 minutes
                if (totalSec >= 300) {
                    state.ui.inactivityTimerEl.style.color = 'red';
                } else if (totalSec >= 240) {
                    state.ui.inactivityTimerEl.style.color = 'orange';
                } else {
                    state.ui.inactivityTimerEl.style.color = '#ffeb3b';
                }
                state.ui.inactivityTimerEl.textContent = `Inactivity: ${txt}`;
            };
            tick();
            state.ui.inactivityTimerIntervalId = setInterval(tick, 1000);
        },

        ensureOpponentStatus: () => {
            if (!storage.get('opponentStatusTimerEnabled', true)) { ui.removeOpponentStatus(); return; }
            const chatRoot = document.querySelector('.root___lv7vM');
            if (!chatRoot) return;
            const allEls = document.querySelectorAll('#tdm-opponent-status');
            if (allEls.length > 1) {
                for (let i = 1; i < allEls.length; i++) allEls[i].remove();
            }
            const existing = document.getElementById('tdm-opponent-status');
            if (existing && existing !== state.ui.opponentStatusEl) {
                state.ui.opponentStatusEl = existing;
            }
            if (state.ui.opponentStatusEl && !document.body.contains(state.ui.opponentStatusEl)) {
                state.ui.opponentStatusEl = null;
            }
            if (!state.ui.opponentStatusEl) {
                const el = utils.createElement('div', {
                    id: 'tdm-opponent-status',
                    className: 'tdm-text-halo',
                    style: { display: 'inline-flex', alignItems: 'center', gap: '4px', marginRight: '8px', color: '#9dd6ff', fontWeight: '600', fontSize: '12px', padding: '1px 1px' }
                });
                chatRoot.insertBefore(el, chatRoot.firstChild?.nextSibling?.nextSibling || chatRoot.firstChild);
                state.ui.opponentStatusEl = el;
                ui.ensureApiUsageBadge();
            } else if (state.ui.opponentStatusEl.parentNode !== chatRoot) {
                chatRoot.insertBefore(state.ui.opponentStatusEl, chatRoot.firstChild?.nextSibling?.nextSibling || chatRoot.firstChild);
            }
            ui.startOpponentStatusUpdater();
        },
        ensureApiUsageBadge: () => {
            if (!storage.get('apiUsageCounterEnabled', true)) return;
            const chatRoot = document.querySelector('.root___lv7vM');
            if (!chatRoot) return;
            let el = document.getElementById('tdm-api-usage');
            if (!el) {
                el = utils.createElement('div', {
                    id: 'tdm-api-usage',
                    className: 'tdm-text-halo',
                    title: 'Torn API calls (session, user key only)',
                    style: { display: 'inline-flex', alignItems: 'center', gap: '4px', marginRight: '8px', color: '#cddc39', fontWeight: '700', fontSize: '12px', padding: '1px 2px' }
                }, [document.createTextNode('API: 0')]);
                chatRoot.insertBefore(el, chatRoot.firstChild);
            } else if (el.parentNode !== chatRoot) {
                chatRoot.insertBefore(el, chatRoot.firstChild);
            }
            ui.updateApiUsageBadge();
        },
        updateApiUsageBadge: () => {
            const el = document.getElementById('tdm-api-usage');
            if (!el) return;
            el.textContent = `API: ${state.session.apiCalls || 0}`;
            el.style.display = storage.get('apiUsageCounterEnabled', true) ? 'inline-flex' : 'none';
        },
        removeOpponentStatus: () => {
            if (state.ui.opponentStatusIntervalId) { clearInterval(state.ui.opponentStatusIntervalId); state.ui.opponentStatusIntervalId = null; }
            if (state.ui.opponentStatusEl) { state.ui.opponentStatusEl.remove(); state.ui.opponentStatusEl = null; }
        },
        startOpponentStatusUpdater: () => {
            if (!state.ui.opponentStatusEl) return;
            if (state.ui.opponentStatusIntervalId) return;

            const getMyTargets = () => {
                // Find my active dibs or med deals from current page context
                const dib = state.dibsData.find(d => d.dibsActive && d.userId === state.user.tornId);
                let medOppId = null;
                for (const [oid, s] of Object.entries(state.opponentStatuses || {})) {
                    if (s?.isMedDeal && s.medDealForUserId === state.user.tornId) { medOppId = oid; break; }
                }
                return { dibOppId: dib?.opponentId || null, medOppId };
            };

            const tick = async () => {
                const { dibOppId, medOppId } = getMyTargets();
                const oppId = dibOppId || medOppId || null;
                if (!oppId) { state.ui.opponentStatusEl.style.display = 'none'; return; }

                // Try to reuse cached hospital release time for the same opponent
                const nowMs = Date.now();
                if (state.ui.opponentStatusCache.opponentId !== oppId) {
                    state.ui.opponentStatusCache = { lastFetch: 0, untilEpoch: 0, text: '', opponentId: oppId };
                }
                // Only refresh every 10s
                if (nowMs - state.ui.opponentStatusCache.lastFetch >= 10000) {
                    state.ui.opponentStatusCache.lastFetch = nowMs;
                    try {
                        const user = await api.getTornUser(state.user.actualTornApiKey, oppId);
                        let text = '';
                        let until = 0;
                        if (user?.status?.state === 'Hospital') {
                            // Torn v2 user has status until epoch seconds
                            until = Number(user.status.until) || 0;
                            text = 'Hosp';
                        } else {
                            text = user?.status?.description || user?.status?.state || 'Okay';
                        }
                        state.ui.opponentStatusCache.untilEpoch = until;
                        state.ui.opponentStatusCache.text = text;
                    } catch (_) { /* noop */ }
                }

                const until = state.ui.opponentStatusCache.untilEpoch;
                const baseText = state.ui.opponentStatusCache.text || '';
                let display = baseText;
                if (baseText === 'Hosp' && until > 0) {
                    const now = Math.floor(Date.now() / 1000);
                    const rem = until - now;
                    if (rem > 0) {
                        const mm = Math.floor(rem / 60);
                        const ss = (rem % 60).toString().padStart(2, '0');
                        display = `Hosp ${mm}:${ss}`;
                    } else {
                        display = 'Okay';
                    }
                }

                state.ui.opponentStatusEl.style.display = 'inline-flex';
                state.ui.opponentStatusEl.style.padding = '1px 1px';
                // Make clickable to the attack page for current opponent
                const href = `https://www.torn.com/loader.php?sid=attack&user2ID=${oppId}`;
                // No target attribute: we control navigation in the handler to avoid double-open
                state.ui.opponentStatusEl.innerHTML = `<a class="tdm-halo-link" href="${href}" rel="noopener noreferrer">Opponent: ${display}</a>`;
                // Some Torn containers intercept anchor clicks; ensure opening via JS
                const anchor = state.ui.opponentStatusEl.querySelector('a');
                if (anchor) {
                    // Make sure the element can receive pointer events and is above overlays
                    try {
                        state.ui.opponentStatusEl.style.pointerEvents = 'auto';
                        state.ui.opponentStatusEl.style.position = 'relative';
                        state.ui.opponentStatusEl.style.zIndex = '2147483647';
                        anchor.style.pointerEvents = 'auto';
                        anchor.style.cursor = 'pointer';
                        anchor.style.textDecoration = 'underline';
                        anchor.style.color = 'inherit';
                        anchor.setAttribute('title', 'Open attack page');
                        anchor.setAttribute('tabindex', '0');
                    } catch (_) { /* noop */ }

                    let opening = false;
                    const openAttack = (e) => {
                        if (opening) return false;
                        opening = true;
                        try {
                            if (e && typeof e.preventDefault === 'function') e.preventDefault();
                            if (e && typeof e.stopImmediatePropagation === 'function') e.stopImmediatePropagation();
                            if (e && typeof e.stopPropagation === 'function') e.stopPropagation();
                        } catch (_) { /* noop */ }
                        let opened = false;
                        try { const w = window.open(href, '_blank', 'noopener'); opened = !!w; } catch (_) { /* noop */ }
                        if (!opened) {
                            try { window.location.href = href; } catch (_) { /* noop */ }
                        }
                        // Reset guard on next tick in case element persists
                        setTimeout(() => { opening = false; }, 0);
                        return false;
                    };
                    // Use capture to get ahead of site-level handlers; single handler to avoid duplicate firing
                    anchor.addEventListener('click', openAttack, { capture: true });
                    anchor.addEventListener('keydown', (e) => {
                        if (e.key === 'Enter' || e.key === ' ') openAttack(e);
                    }, { capture: true });
                }
            };

            tick();
            state.ui.opponentStatusIntervalId = setInterval(tick, 1000);
        },

        processRankedWarTables: async () => {
            // utils.perf.start('processRankedWarTables');
            const factionTables = state.dom.rankwarfactionTables;
            if (!factionTables || !factionTables.length) { 
                // utils.perf.stop('processRankedWarTables'); 
                return; 
            }

            state.script.hasProcessedRankedWarTables = true;
            const ourFactionName = state.factionPull?.name;

            const opponentFactionLink = state.dom.rankBox?.querySelector('.nameWp___EX6gT .opponentFactionName___vhESM');
            const currentFactionLink = state.dom.rankBox?.querySelector('.nameWp___EX6gT .currentFactionName___eq7n8');

            const opponentFactionName = opponentFactionLink ? opponentFactionLink.textContent.trim() : 'N/A';
            const currentFactionName = currentFactionLink ? currentFactionLink.textContent.trim() : 'N/A';

            factionTables.forEach(tableContainer => {
                let isCurrentTableOurFaction = false;
                if (tableContainer.classList.contains('left')) {
                    if (ourFactionName && opponentFactionName === ourFactionName) isCurrentTableOurFaction = true;
                } else if (tableContainer.classList.contains('right')) {
                    if (ourFactionName && currentFactionName === ourFactionName) isCurrentTableOurFaction = true;
                }

                tableContainer.querySelectorAll('.members-list > li').forEach(row => {
                    if (row.querySelector('.dibs-notes-subrow')) return; // Already processed

                    const userLink = row.querySelector('a[href*="profiles.php?XID="]');
                    if (!userLink) return;

                    let subrow = utils.createElement('div', { className: 'dibs-notes-subrow' });

                    if (!isCurrentTableOurFaction) {
                        // Create placeholders for all buttons to prevent layout shifts
                        subrow.appendChild(utils.createElement('button', { className: 'btn dibs-btn btn-dibs-inactive', innerHTML: '<span class="dibs-spinner"></span>', disabled: true }));
                        subrow.appendChild(utils.createElement('button', { className: 'btn btn-med-deal-default', innerHTML: '<span class="dibs-spinner"></span>', disabled: true, style: { display: 'none' } }));
                        subrow.appendChild(utils.createElement('button', { className: 'btn note-button inactive-note-button', innerHTML: '<span class="dibs-spinner"></span>', disabled: true }));

                        // Retals placeholder
                        const retalContainer = utils.createElement('div', { style: { flexGrow: '1', display: 'flex', justifyContent: 'flex-end' } });
                        retalContainer.appendChild(utils.createElement('button', { className: 'btn retal-btn', innerHTML: '<span class="dibs-spinner"></span>', disabled: true, style: { display: 'none' } }));
                        subrow.appendChild(retalContainer);
                    } else {
                        // For our own faction, just add a notes button placeholder
                        subrow.appendChild(utils.createElement('button', { className: 'btn note-button inactive-note-button', innerHTML: '<span class="dibs-spinner"></span>', disabled: true }));
                    }

                    const lastActionRow = row.querySelector('.last-action-row');
                    if (lastActionRow) subrow.appendChild(lastActionRow);

                    row.appendChild(subrow);
                });
            });
            ui.updateRankedWarUI(); // Immediately update with any available data
            // utils.perf.stop('processRankedWarTables');
        },

        processFactionPageMembers: async (container) => {
            // utils.perf.start('processFactionPageMembers');
            const members = container.querySelectorAll('.table-body > li.table-row');
            if (!members.length) { 
                // utils.perf.stop('processFactionPageMembers'); 
                return; 
            }

            const headerUl = container.querySelector('.table-header');
            if (headerUl && !headerUl.querySelector('#col-header-dibs-notes')) {
                // Add a single, combined header
                headerUl.appendChild(utils.createElement('li', { id: 'col-header-dibs-notes', className: 'table-cell table-header-column', innerHTML: `<span>Dibs/Notes</span>` }));
            }

            members.forEach(row => {
                if (row.querySelector('.tdm-controls-container')) return; // Already processed

                // Create a single container for all our controls
                const controlsContainer = utils.createElement('div', { className: 'table-cell tdm-controls-container' });

                // Create Dibs and Med Deal button placeholders
                const dibsCell = utils.createElement('div', { className: 'dibs-cell' });
                dibsCell.appendChild(utils.createElement('button', { className: 'btn dibs-button', innerHTML: '<span class="dibs-spinner"></span>' }));
                dibsCell.appendChild(utils.createElement('button', { className: 'btn med-deal-button', style: { display: 'none' }, innerHTML: '<span class="dibs-spinner"></span>' }));

                // Create Note button placeholder
                const notesCell = utils.createElement('div', { className: 'notes-cell' });
                notesCell.appendChild(utils.createElement('button', { className: 'btn note-button', innerHTML: '<span class="dibs-spinner"></span>' }));

                controlsContainer.appendChild(dibsCell);
                controlsContainer.appendChild(notesCell);
                row.appendChild(controlsContainer);
            });
            ui.updateFactionPageUI(container);
            // utils.perf.stop('processFactionPageMembers');
        },

        updateRankedWarUI: () => {
            // utils.perf.start('updateRankedWarUI');
            const factionTables = state.dom.rankwarfactionTables;
            if (!factionTables) { 
                // utils.perf.stop('updateRankedWarUI'); 
                return; 
                }
            const ourFactionName = state.factionPull?.name;

            const opponentFactionLink = state.dom.rankBox?.querySelector('.nameWp___EX6gT .opponentFactionName___vhESM');
            const currentFactionLink = state.dom.rankBox?.querySelector('.nameWp___EX6gT .currentFactionName___eq7n8');
            const opponentFactionName = opponentFactionLink ? opponentFactionLink.textContent.trim() : 'N/A';
            const currentFactionName = currentFactionLink ? currentFactionLink.textContent.trim() : 'N/A';

            factionTables.forEach(tableContainer => {
                let isCurrentTableOurFaction = false;
                if (tableContainer.classList.contains('left')) {
                    if (ourFactionName && opponentFactionName === ourFactionName) isCurrentTableOurFaction = true;
                } else if (tableContainer.classList.contains('right')) {
                    if (ourFactionName && currentFactionName === ourFactionName) isCurrentTableOurFaction = true;
                }

                tableContainer.querySelectorAll('.members-list > li').forEach(row => {
                    const userLink = row.querySelector('a[href*="profiles.php?XID="]');
                    if (!userLink) return;
                    const opponentId = userLink.href.match(/XID=(\d+)/)[1];
                    const opponentName = userLink.textContent.trim();
                    let subrow = row.querySelector('.dibs-notes-subrow');
                    if (!subrow) return;

                    // --- Conditional Notes Button Update START ---
                    let notesBtn = subrow.querySelector('.note-button'); // Generic class
                    if (notesBtn) {
                        const userNote = state.userNotes[opponentId];
                        const newNoteText = userNote?.noteContent || 'Note';
                        const newNoteClass = 'btn note-button ' + (userNote?.noteContent ? 'active-note-button' : 'inactive-note-button');
                        if (notesBtn.textContent !== newNoteText) {
                            notesBtn.textContent = newNoteText;
                            notesBtn.title = userNote?.noteContent || '';
                        }
                        if (notesBtn.className !== newNoteClass) notesBtn.className = newNoteClass;
                        notesBtn.onclick = (e) => ui.openNoteModal(opponentId, opponentName, userNote?.noteContent || '', e.currentTarget);
                        notesBtn.disabled = false;
                    }
                    // --- Conditional Notes Button Update END ---

                    if (!isCurrentTableOurFaction) {
                        // --- Conditional Dibs Button Update START ---
            let dibsBtn = subrow.querySelector('.dibs-btn');
                        if (dibsBtn) {
                            const activeDibs = state.dibsData.find(d => d.opponentId === opponentId && d.dibsActive);
                            let newDibsText, newDibsClass, newDibsDisabled;
                            if (activeDibs) {
                                newDibsText = activeDibs.userId === state.user.tornId ? 'YOU Dibbed' : activeDibs.username;
                                newDibsClass = 'btn dibs-btn ' + (activeDibs.userId === state.user.tornId ? 'btn-dibs-success-you' : 'btn-dibs-success-other');
                                newDibsDisabled = !(activeDibs.userId === state.user.tornId || (state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true));
                                dibsBtn.onclick = (e) => handlers.debouncedRemoveDibsForTarget(opponentId, e.currentTarget);
                            } else {
                                newDibsText = 'Dibs';
                                newDibsClass = 'btn dibs-btn btn-dibs-inactive';
                // Policy-based local disablement (user status only to avoid per-row API calls)
                const opts = utils.getDibsStyleOptions();
                const myCanon = utils.getMyCanonicalStatus();
                newDibsDisabled = opts?.allowedUserStatuses && opts.allowedUserStatuses[myCanon] === false;
                                dibsBtn.onclick = (state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true)
                                    ? (e) => ui.openDibsSetterModal(opponentId, opponentName, e.currentTarget)
                                    : (e) => handlers.debouncedDibsTarget(opponentId, opponentName, e.currentTarget);
                if (newDibsDisabled) dibsBtn.title = `Disabled by policy: Your status (${myCanon})`;
                            }
                            if (dibsBtn.textContent !== newDibsText) dibsBtn.textContent = newDibsText;
                            if (dibsBtn.className !== newDibsClass) dibsBtn.className = newDibsClass;
                            if (dibsBtn.disabled !== newDibsDisabled) dibsBtn.disabled = newDibsDisabled;
                        }
                        // --- Conditional Dibs Button Update END ---


                        // --- Conditional Med Deal Button Update START ---
                        let medDealBtn = subrow.querySelector('.btn-med-deal-default');
                        if (medDealBtn) {
                            if (state.warData.warType === 'Termed War') {
                                medDealBtn.style.display = 'inline-block';
                                const medDealStatus = state.opponentStatuses[opponentId];
                                const isMedDealActive = medDealStatus?.isMedDeal;
                                const isMyMedDeal = isMedDealActive && medDealStatus.medDealForUserId === state.user.tornId;
                                let newMedDealHTML, newMedDealClass, newMedDealDisabled;

                                if (isMyMedDeal) {
                                    newMedDealHTML = 'Remove Deal';
                                    newMedDealClass = 'btn btn-med-deal-default btn-med-deal-mine';
                                    newMedDealDisabled = false;
                                    medDealBtn.onclick = (e) => handlers.debouncedHandleMedDealToggle(opponentId, opponentName, false, state.user.tornId, state.user.tornUsername, e.currentTarget);
                                } else if (isMedDealActive) {
                                    newMedDealHTML = `${medDealStatus.medDealForUsername}`;
                                    newMedDealClass = 'btn btn-med-deal-default btn-med-deal-set';
                                    newMedDealDisabled = !(state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true);
                                    medDealBtn.onclick = (e) => handlers.debouncedHandleMedDealToggle(opponentId, opponentName, false, medDealStatus.medDealForUserId || opponentId, medDealStatus.medDealForUsername || opponentName, e.currentTarget);
                                } else {
                                    newMedDealHTML = 'Set Deal';
                                    newMedDealClass = 'btn btn-med-deal-default btn-med-deal-inactive';
                                    newMedDealDisabled = false;
                                    medDealBtn.onclick = (state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true)
                                        ? (e) => ui.openMedDealSetterModal(opponentId, opponentName, e.currentTarget)
                                        : (e) => handlers.debouncedHandleMedDealToggle(opponentId, opponentName, true, state.user.tornId, state.user.tornUsername, e.currentTarget);
                                }
                                if (medDealBtn.innerHTML !== newMedDealHTML) medDealBtn.innerHTML = newMedDealHTML;
                                if (medDealBtn.className !== newMedDealClass) medDealBtn.className = newMedDealClass;
                                if (medDealBtn.disabled !== newMedDealDisabled) medDealBtn.disabled = newMedDealDisabled;
                            } else {
                                medDealBtn.style.display = 'none';
                            }
                        }
                        // --- Conditional Med Deal Button Update END ---
                        
                        // Retal Button
                        let retalBtn = subrow.querySelector('.retal-btn');
                        if (retalBtn) ui.updateRetaliationButton(retalBtn, opponentId, opponentName);
                    }
                });
            });
            // utils.perf.stop('updateRankedWarUI');
        },

        updateFactionPageUI: (container) => {
            utils.perf.start('updateFactionPageUI');
            const members = container.querySelectorAll('.f-war-list .table-body > li.table-row');
            if (!members.length) { 
                // utils.perf.stop('updateFactionPageUI'); 
                return; 
            }

            members.forEach(memberLi => {
                const controlsContainer = memberLi.querySelector('.tdm-controls-container');
                if (!controlsContainer) return;

                const memberIdLink = memberLi.querySelector('a[href*="profiles.php?XID="]');
                if (!memberIdLink) return;

                const opponentId = memberIdLink.href.match(/XID=(\d+)/)[1];
                const opponentName = String(memberIdLink.textContent || '').trim() || `Opponent (${opponentId})`;

                const dibsCell = controlsContainer.querySelector('.dibs-cell');
                const notesCell = controlsContainer.querySelector('.notes-cell');

                // --- Update Dibs & Med Deal Cell ---
                if (state.page.isMyFactionPage) {
                    dibsCell.style.display = 'none';
                } else {
                    dibsCell.style.display = 'flex';
                    const dibsButton = dibsCell.querySelector('.dibs-button');
                    const medDealButton = dibsCell.querySelector('.med-deal-button');

                    // --- Conditional Dibs Button Update START ---
                    const activeDibs = state.dibsData.find(d => d.opponentId === opponentId && d.dibsActive);
                    let newDibsText, newDibsClass, newDibsDisabled;
                    if (activeDibs) {
                        newDibsText = activeDibs.userId === state.user.tornId ? 'YOU Dibbed' : activeDibs.username;
                        newDibsClass = 'btn dibs-button ' + (activeDibs.userId === state.user.tornId ? 'btn-dibs-success-you' : 'btn-dibs-success-other');
                        newDibsDisabled = !(activeDibs.userId === state.user.tornId || (state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true));
                        dibsButton.onclick = (e) => handlers.debouncedRemoveDibsForTarget(opponentId, e.currentTarget);
                    } else {
                        newDibsText = 'Dibs';
                        newDibsClass = 'btn dibs-button btn-dibs-inactive';
                        const opts = utils.getDibsStyleOptions();
                        const myCanon = utils.getMyCanonicalStatus();
                        newDibsDisabled = opts?.allowedUserStatuses && opts.allowedUserStatuses[myCanon] === false;
                        dibsButton.onclick = (state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true)
                            ? (e) => ui.openDibsSetterModal(opponentId, opponentName, e.currentTarget)
                            : (e) => handlers.debouncedDibsTarget(opponentId, opponentName, e.currentTarget);
                        if (newDibsDisabled) dibsButton.title = `Disabled by policy: Your status (${myCanon})`;
                    }
                    if (dibsButton.textContent !== newDibsText) dibsButton.textContent = newDibsText;
                    if (dibsButton.className !== newDibsClass) dibsButton.className = newDibsClass;
                    if (dibsButton.disabled !== newDibsDisabled) dibsButton.disabled = newDibsDisabled;
                    // --- Conditional Dibs Button Update END ---

                    // --- Conditional Med Deal Button Update START ---
                    if (state.warData.warType === 'Termed War') {
                        medDealButton.style.display = 'flex';
                        const medDealStatus = state.opponentStatuses[opponentId];
                        const isMedDealActive = medDealStatus?.isMedDeal;
                        const isMyMedDeal = isMedDealActive && medDealStatus.medDealForUserId === state.user.tornId;
                        let newMedDealHTML, newMedDealClass, newMedDealDisabled;

                        if (isMyMedDeal) {
                            newMedDealHTML = 'Remove Deal';
                            newMedDealClass = 'btn med-deal-button btn-med-deal-mine';
                            newMedDealDisabled = false;
                            medDealButton.onclick = (e) => handlers.debouncedHandleMedDealToggle(opponentId, opponentName, false, state.user.tornId, state.user.tornUsername, e.currentTarget);
                        } else if (isMedDealActive) {
                            newMedDealHTML = `${medDealStatus.medDealForUsername || 'Someone'}`;
                            newMedDealClass = 'btn med-deal-button btn-med-deal-set';
                            newMedDealDisabled = !(state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true);
                            medDealButton.onclick = (e) => handlers.debouncedHandleMedDealToggle(opponentId, opponentName, false, medDealStatus.medDealForUserId, medDealStatus.medDealForUsername, e.currentTarget);
                        } else {
                            newMedDealHTML = 'Set Deal';
                            newMedDealClass = 'btn med-deal-button btn-med-deal-inactive';
                            newMedDealDisabled = false;
                            medDealButton.onclick = (state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true)
                                ? (e) => ui.openMedDealSetterModal(opponentId, opponentName, e.currentTarget)
                                : (e) => handlers.debouncedHandleMedDealToggle(opponentId, opponentName, true, state.user.tornId, state.user.tornUsername, e.currentTarget);
                        }
                        if (medDealButton.innerHTML !== newMedDealHTML) medDealButton.innerHTML = newMedDealHTML;
                        if (medDealButton.className !== newMedDealClass) medDealButton.className = newMedDealClass;
                        if (medDealButton.disabled !== newMedDealDisabled) medDealButton.disabled = newMedDealDisabled;
                    } else {
                        medDealButton.style.display = 'none';
                    }
                    // --- Conditional Med Deal Button Update END ---
                }

                // --- Conditional Notes Cell Update START ---
                const noteButton = notesCell.querySelector('.note-button');
                const userNote = state.userNotes[opponentId];
                const newNoteText = userNote?.noteContent || 'Note';
                const newNoteClass = 'btn note-button ' + (userNote?.noteContent ? 'active-note-button' : 'inactive-note-button');
                if (noteButton.textContent !== newNoteText) {
                    noteButton.textContent = newNoteText;
                    noteButton.title = userNote?.noteContent || '';
                }
                if (noteButton.className !== newNoteClass) noteButton.className = newNoteClass;
                noteButton.onclick = (e) => ui.openNoteModal(opponentId, opponentName, userNote?.noteContent || '', e.currentTarget);
                noteButton.disabled = false;
                // --- Conditional Notes Cell Update END ---
            });
            // utils.perf.stop('updateFactionPageUI');
        },

        injectAttackPageUI: async () => {
            const opponentId = new URLSearchParams(window.location.search).get('user2ID');
            if (!opponentId) return;

            ui.createSettingsButton();
            const appHeaderWrapper = document.querySelector('.playersModelWrap___dkqHO');
            if (!appHeaderWrapper) return;
            utils.perf.start('injectAttackPageUI');
            let attackContainer = document.getElementById('tdm-attack-container');
            if (!attackContainer) {
                attackContainer = utils.createElement('div', { id: 'tdm-attack-container', style: { margin: '10px', padding: '10px', background: '#2d2c2c', borderRadius: '5px', border: '1px solid #444', textAlign: 'center', color: 'white' } });
                appHeaderWrapper.insertAdjacentElement('afterend', attackContainer);
            }

            const opponentName = document.querySelector('.players___eKiHL')?.querySelectorAll('span[id^="playername_"]')[1]?.textContent.trim() ?? `Opponent ID (${opponentId})`;

            // --- FIX START: Build new content in a fragment to prevent flicker ---
            const contentFragment = document.createDocumentFragment();

            // Defer Score Cap Notification (non-blocking UI)
            // Buttons render immediately; warning is verified asynchronously and updated in place
            setTimeout(() => {
                const existingWarning = attackContainer.querySelector('.score-cap-warning');
                if (!state.user.hasReachedScoreCap) {
                    if (existingWarning) existingWarning.remove();
                    return;
                }
                (async () => {
                    try {
                        const opponentUser = await api.getTornUser(state.user.actualTornApiKey, opponentId);
                        if (opponentUser?.faction?.faction_id?.toString() === state.warData.opponentFactionId?.toString()) {
                            if (!existingWarning) {
                                const scoreCapWarning = utils.createElement('div', {
                                    className: 'score-cap-warning',
                                    style: { padding: '10px', marginBottom: '10px', backgroundColor: config.CSS.colors.error, color: 'white', borderRadius: '5px', fontWeight: 'bold' },
                                    textContent: 'SCORE CAP REACHED! Do not attack.'
                                });
                                attackContainer.insertBefore(scoreCapWarning, attackContainer.firstChild);
                            }
                        } else if (existingWarning) {
                            existingWarning.remove();
                        }
                    } catch (error) {
                        console.error("[TDM] Failed to verify opponent's faction for score cap warning:", error);
                    }
                })();
            }, 0);

            try {
                // Build the button rows
                const opponentDibs = state.dibsData.find(d => d.opponentId === opponentId && d.dibsActive);
                const opponentMedDeal = state.opponentStatuses[opponentId];
                const opponentNote = state.userNotes[opponentId];

                const buttonRow = utils.createElement('div', { style: { display: 'flex', gap: '4px', justifyContent: 'center', flexWrap: 'nowrap', marginBottom: '8px' } });
                // ... (Button creation logic is the same)
                const dibsBtn = utils.createElement('button', { className: 'btn dibs-btn', style: { minWidth: '70px', maxWidth: '70px', minHeight: '24px', boxSizing: 'border-box', fontSize: '0.75em', padding: '4px 6px', borderColor: '#004d4f !important', borderRadius: '3px' } });
                if (opponentDibs) {
                    if (opponentDibs.userId === state.user.tornId) {
                        dibsBtn.textContent = 'YOU Dibbed';
                        dibsBtn.classList.add('btn-dibs-success-you');
                        dibsBtn.onclick = (e) => handlers.debouncedRemoveDibsForTarget(opponentId, e.currentTarget);
                    } else {
                        dibsBtn.textContent = opponentDibs.username;
                        dibsBtn.classList.add('btn-dibs-success-other');
                        dibsBtn.disabled = !(state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true);
                        if (!dibsBtn.disabled) dibsBtn.onclick = (e) => handlers.debouncedRemoveDibsForTarget(opponentId, e.currentTarget);
                    }
                } else {
                    dibsBtn.textContent = 'Dibs';
                    dibsBtn.classList.add('btn-dibs-inactive');
                    dibsBtn.onclick = (state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true)
                        ? (e) => ui.openDibsSetterModal(opponentId, opponentName, e.currentTarget)
                        : (e) => handlers.debouncedDibsTarget(opponentId, opponentName, e.currentTarget);
                    // Light, non-blocking policy gating for attack page button
                    (async () => {
                        try {
                            const opts = utils.getDibsStyleOptions();
                            const myCanon = utils.getMyCanonicalStatus();
                            if (opts.allowedUserStatuses && opts.allowedUserStatuses[myCanon] === false) {
                                dibsBtn.disabled = true;
                                dibsBtn.title = `Disabled by policy: Your status (${myCanon}) cannot place dibs`;
                                return;
                            }
                            // If we can afford one status check, validate opponent allowed statuses
                            const oppStatus = await utils.getUserStatus(opponentId);
                            const canonOpp = oppStatus.canonical;
                            if (opts.allowStatuses && opts.allowStatuses[canonOpp] === false) {
                                dibsBtn.disabled = true;
                                dibsBtn.title = `Disabled by policy: Opponent status (${canonOpp})`;
                            }
                        } catch (_) { /* non-fatal */ }
                    })();
                }
                buttonRow.appendChild(dibsBtn);

                if (state.warData.warType === 'Termed War') {
                    const medDealBtn = utils.createElement('button', { className: 'btn med-deal-btn', style: { minWidth: '70px', maxWidth: '70px', minHeight: '24px', boxSizing: 'border-box', fontSize: '0.75em', padding: '4px 6px', borderColor: '#004d4f !important', borderRadius: '3px' } });
                    const isMedDealActive = opponentMedDeal?.isMedDeal;
                    const isMyMedDeal = isMedDealActive && opponentMedDeal.medDealForUserId === state.user.tornId;

                    if (isMyMedDeal) {
                        medDealBtn.innerHTML = 'Remove Deal';
                        medDealBtn.classList.add('btn-med-deal-mine');
                        medDealBtn.onclick = (e) => handlers.debouncedHandleMedDealToggle(opponentId, opponentName, false, state.user.tornId, state.user.tornUsername, e.currentTarget);
                    } else if (isMedDealActive) {
                        medDealBtn.innerHTML = `${opponentMedDeal.medDealForUsername}`;
                        medDealBtn.classList.add('btn-med-deal-set');
                        medDealBtn.style.whiteSpace = 'normal';
                        medDealBtn.disabled = !(state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true);
                        if (!medDealBtn.disabled) medDealBtn.onclick = (e) => handlers.debouncedHandleMedDealToggle(opponentId, opponentName, false, opponentMedDeal.medDealForUserId, opponentMedDeal.medDealForUsername, e.currentTarget);
                    } else {
                        medDealBtn.innerHTML = 'Set Deal';
                        medDealBtn.classList.add('btn-med-deal-inactive');
                        medDealBtn.style.whiteSpace = 'normal';
                        medDealBtn.onclick = (state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true)
                            ? (e) => ui.openMedDealSetterModal(opponentId, opponentName, e.currentTarget)
                            : (e) => handlers.debouncedHandleMedDealToggle(opponentId, opponentName, true, state.user.tornId, state.user.tornUsername, e.currentTarget);
                    }
                    buttonRow.appendChild(medDealBtn);
                }

                const noteContent = opponentNote?.noteContent || '';
                const notesBtn = utils.createElement('button', { textContent: noteContent || 'Note', title: noteContent, className: 'btn ' + (noteContent.trim() !== '' ? 'active-note-button' : 'inactive-note-button'), style: { minWidth: '70px', maxWidth: '70px', minHeight: '24px', boxSizing: 'border-box', fontSize: '0.75em', padding: '4px 6px', borderColor: '#004d4f !important', borderRadius: '3px' }, onclick: (e) => ui.openNoteModal(opponentId, opponentName, noteContent, e.currentTarget) });
                buttonRow.appendChild(notesBtn);

                // Always create a Retal button placeholder; updater will control visibility
                const retalBtn = utils.createElement('button', { className: 'btn retal-btn btn-retal-inactive', style: { minWidth: '70px', maxWidth: '70px', minHeight: '24px', boxSizing: 'border-box', fontSize: '0.75em', padding: '4px 6px', borderColor: '#004d4f !important', borderRadius: '3px', marginLeft: 'auto', display: 'none' }, disabled: true, onclick: () => ui.sendRetaliationAlert(opponentId, opponentName) });
                buttonRow.appendChild(retalBtn);
                ui.updateRetaliationButton(retalBtn, opponentId, opponentName);
                contentFragment.appendChild(buttonRow);

                const assistRow = utils.createElement('div', { style: { display: 'flex', gap: '4px', justifyContent: 'center', flexWrap: 'wrap' } });
                assistRow.appendChild(utils.createElement('span', { textContent: 'Need Assistance:', style: { alignSelf: 'center', fontSize: '0.9em', color: '#ffffffff', marginRight: '2px' } }));
                const assistanceButtons = [{ text: 'Smoke/Flash (Speed)', message: 'Need Smoke/Flash on' }, { text: 'Tear/Pepper (Dex)', message: 'Need Tear/Pepper on' }, { text: 'Help Kill', message: 'Help Kill' }, { text: 'Target Down', message: 'Target Down' }];
                assistanceButtons.forEach(btnInfo => {
                    assistRow.appendChild(utils.createElement('button', { className: 'btn req-assist-button', textContent: btnInfo.text, onclick: () => ui.sendAssistanceRequest(btnInfo.message, opponentId, opponentName), style: { minWidth: '70px', maxWidth: '70px', minHeight: '24px', boxSizing: 'border-box', fontSize: '0.75em', padding: '4px 6px', borderColor: '#004d4f !important', borderRadius: '3px' } }));
                });
                contentFragment.appendChild(assistRow);

                // Replace only the button/assist rows, leaving the warning intact
                const rowsToRemove = attackContainer.querySelectorAll('div:not(.score-cap-warning)');
                rowsToRemove.forEach(row => row.remove());
                attackContainer.appendChild(contentFragment);
                // --- FIX END ---
            } catch (error) {
                console.error("[TDM] Error in attack page UI injection:", error);
                attackContainer.innerHTML = '<p style="color: #ff6b6b;">Error loading attack page UI</p>';
            }
            utils.perf.stop('injectAttackPageUI');
        },

        sendAssistanceRequest: (message, opponentId, opponentName) => {
            const opponentLink = `<a href="https://www.torn.com/loader.php?sid=attack&user2ID=${opponentId}" target="_blank">${opponentName}</a>`;
            const fullMessage = `TDM - ${message} ${opponentLink}`;
            const facId = state.user.factionId;
            // Try to locate currently open chat textarea
            let chatTextbox = document.querySelector('div.root___WUd1h textarea.textarea___V8HsV');
            // Or get the faction button by id
            const chatButton = document.querySelector(`#channel_panel_button\\:faction-${facId}`);
            if (!chatTextbox && chatButton) {
                chatButton.click();
                setTimeout(() => ui.populateChatMessage(fullMessage),700);
            } else if (chatTextbox) {
                setTimeout(() => ui.populateChatMessage(fullMessage),500);
            }
        },
        populateChatMessage(message) {
            const chatTextArea = document.querySelector('textarea.textarea___V8HsV');

            if (chatTextArea) {
                // 1. Set the value of the textarea.
                // This might be read by the component during its sync process.
                chatTextArea.value = message;

                // Optional: Dispatch the 'input' event.
                // Keep this in case the component also relies on it in combination with the mutation.
                const inputEvent = new Event('input', { bubbles: true });
                chatTextArea.dispatchEvent(inputEvent);

                // 2. Trigger a DOM mutation to force the component to sync.
                // Toggling a data attribute is a reliable and non-visual way to do this.
                const dataAttributeName = 'data-userscript-synced'; // Use a distinct attribute name
                if (chatTextArea.hasAttribute(dataAttributeName)) {
                    chatTextArea.removeAttribute(dataAttributeName);
                } else {
                    chatTextArea.setAttribute(dataAttributeName, Date.now().toString()); // Add or update the attribute
                }

                console.log('[TreeDibsMapper] Textarea value set and DOM mutation triggered.');

                ui.showMessageBox('Message added to faction chat. Click send manually.', 'success');
            } else {
                console.log('[TreeDibsMapper] Chat textbox not found, message:', message);
                document.execCommand('copy'); // Use document.execCommand('copy') for clipboard operations in iframes
                ui.showMessageBox('Chat not found. Message copied to clipboard.', 'info');
            }
        },
        sendRetaliationAlert: (opponentId, opponentName) => {
            const retalOpp = state.retaliationOpportunities[opponentId];
            if (!retalOpp) return;
            const now = Math.floor(Date.now() / 1000);
            const timeRemaining = retalOpp.retaliationEndTime - now;
            let timeStr = 'expired';
            if (timeRemaining > 0) {
                const minutes = Math.floor(timeRemaining / 60);
                const seconds = Math.floor(timeRemaining % 60);
                timeStr = `${minutes}:${seconds.toString().padStart(2, '0')}`;
            }
            const opponentLink = `<a href="https://www.torn.com/loader.php?sid=attack&user2ID=${opponentId}" target="_blank">${opponentName}</a>`;
            const fullMessage = `Retal Available ${opponentLink} time left: ${timeStr} Hospitalize`;
            const facId = state.user.factionId;
            let chatTextbox = document.querySelector('div.root___WUd1h textarea.textarea___V8HsV');
            const chatButton = document.querySelector(`#channel_panel_button\\:faction-${facId}`);
            if (!chatTextbox && chatButton) {
                chatButton.click();
                setTimeout(() => ui.populateChatMessage(fullMessage),700);
            } else if (chatTextbox) {
                setTimeout(() => ui.populateChatMessage(fullMessage),500);
            }
        },

        updateRetaliationButton: (button, opponentId, opponentName) => {
            // Clear any prior interval tied to this button
            if (button._retalIntervalId) {
                clearInterval(button._retalIntervalId);
                button._retalIntervalId = null;
            }

            const show = () => {
                button.style.display = 'inline-block';
                button.disabled = false;
                button.innerHTML = '';
                button.className = 'btn retal-btn btn-retal-active';
                button.onclick = () => ui.sendRetaliationAlert(opponentId, opponentName);
            };

            const hide = () => {
                button.style.display = 'none';
                button.disabled = true;
                button.innerHTML = '';
                button.className = 'btn retal-btn btn-retal-inactive';
                if (button._retalIntervalId) {
                    clearInterval(button._retalIntervalId);
                    button._retalIntervalId = null;
                }
            };

            const computeAndRender = () => {
                const current = state.retaliationOpportunities[opponentId];
                if (!current) {
                    hide();
                    return false;
                }
                const now = Math.floor(Date.now() / 1000);
                const timeRemaining = current.retaliationEndTime - now;
                if (timeRemaining <= 0) {
                    hide();
                    return false;
                }
                show();
                const mm = Math.floor(timeRemaining / 60);
                const ss = ('0' + (timeRemaining % 60)).slice(-2);
                button.textContent = `Retal: ${mm}:${ss}`;
                return true;
            };

            // Initial render
            const active = computeAndRender();
            if (!active) return;

            // Keep ticking; auto-hide when expired or fulfilled
            button._retalIntervalId = setInterval(() => {
                const stillActive = computeAndRender();
                if (!stillActive) {
                    // interval cleared in hide(); just be safe
                    if (button._retalIntervalId) {
                        clearInterval(button._retalIntervalId);
                        button._retalIntervalId = null;
                    }
                }
            }, 1000);
        },

        openNoteModal: (tornID, tornUsername, currentNoteContent, buttonElement) => {
            state.ui.currentNoteButtonElement = buttonElement;
            if (state.ui.currentNoteButtonElement) {
                state.ui.currentNoteButtonElement.dataset.originalText = state.ui.currentNoteButtonElement.textContent;
                state.ui.currentNoteButtonElement.disabled = true;
                state.ui.currentNoteButtonElement.innerHTML = '<span class="dibs-spinner"></span> Loading...';
            }
            if (!state.ui.noteModal) {
                state.ui.noteModal = utils.createElement('div', { id: 'user-note-modal', style: { position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', backgroundColor: '#1a1a1a', border: '1px solid #333', borderRadius: '8px', padding: '20px', zIndex: 10000, boxShadow: '0 4px 8px rgba(0,0,0,0.5)', maxWidth: '400px', width: '90%', color: 'white' } });
                state.ui.noteModal.innerHTML = `
                    <h3 style="margin-top: 0;">Edit User Note for ${tornUsername} (${tornID})</h3>
                    <label for="note-textarea" style="display: block; margin-bottom: 5px;">Note Content:</label>
                    <textarea id="note-textarea" rows="8" style="width: calc(100% - 10px); background-color: #222; border: 1px solid #555; color: white; padding: 5px; border-radius: 4px; margin-bottom: 10px; resize: vertical;"></textarea>
                    <button id="save-note-button" style="background-color: #4CAF50; color: white; border: none; border-radius: 4px; padding: 8px 15px; cursor: pointer; margin-right: 10px;">Save</button>
                    <button id="cancel-note-button" style="background-color: #f44336; color: white; border: none; border-radius: 4px; padding: 8px 15px; cursor: pointer;">Cancel</button>
                `;
                document.body.appendChild(state.ui.noteModal);
                state.ui.noteTextarea = document.getElementById('note-textarea');
                document.getElementById('save-note-button').onclick = () => handlers.debouncedHandleSaveUserNote(state.ui.currentNoteTornID, state.ui.noteTextarea.value.trim(), state.ui.currentNoteButtonElement);
                document.getElementById('cancel-note-button').onclick = ui.closeNoteModal;
            }
            state.ui.currentNoteTornID = tornID;
            state.ui.currentNoteTornUsername = tornUsername;
            state.ui.noteTextarea.value = currentNoteContent;
            state.ui.noteModal.querySelector('h3').textContent = `Edit User Note for ${tornUsername} (${tornID})`;
            state.ui.noteModal.style.display = 'block';
            if (state.ui.currentNoteButtonElement) {
                state.ui.currentNoteButtonElement.disabled = false;
            }
        },

        closeNoteModal: () => {
            if (state.ui.noteModal) state.ui.noteModal.style.display = 'none';
            state.ui.currentNoteTornID = null;
            state.ui.currentNoteTornUsername = null;
            if (state.ui.currentNoteButtonElement) {
                state.ui.currentNoteButtonElement.disabled = false;
                state.ui.currentNoteButtonElement.textContent = state.ui.currentNoteButtonElement.dataset.originalText;
                state.ui.currentNoteButtonElement = null;
            }
        },

        openSetterModal: async (opponentId, opponentName, buttonElement, type) => {
            // If admin functionality is disabled (or user lacks admin rights), treat buttons as simple toggles for self
            const adminEnabled = !!state.script.canAdministerMedDeals && (storage.get('adminFunctionality', true) === true);
            if (!adminEnabled) {
                const defaultText = type === 'medDeal' ? 'Set Med Deal' : 'Dibs';
                try {
                    if (buttonElement) {
                        buttonElement.dataset.originalText = buttonElement.dataset.originalText || buttonElement.textContent;
                        buttonElement.disabled = true;
                        buttonElement.innerHTML = '<span class="dibs-spinner"></span> Working...';
                    }
                    if (type === 'medDeal') {
                        const mds = (state.opponentStatuses || {})[opponentId];
                        const isMyMedDeal = !!(mds && mds.isMedDeal && String(mds.medDealForUserId) === String(state.user.tornId));
                        // Toggle med deal for current user
                        await handlers.debouncedHandleMedDealToggle(
                            opponentId,
                            opponentName,
                            !isMyMedDeal, // set if not mine; remove if mine
                            state.user.tornId,
                            state.user.tornUsername,
                            buttonElement
                        );
                    } else {
                        // type === 'dibs': toggle dib for current user
                        const myActive = (state.dibsData || []).find(d => d.opponentId === opponentId && d.dibsActive && String(d.userId) === String(state.user.tornId));
                        if (myActive) {
                            await handlers.debouncedRemoveDibsForTarget(opponentId, buttonElement);
                        } else {
                            await handlers.debouncedDibsTarget(opponentId, opponentName, buttonElement);
                        }
                    }
                } catch (_) {
                    // noop; handlers already show messages
                } finally {
                    if (buttonElement) {
                        buttonElement.disabled = false;
                        buttonElement.textContent = buttonElement.dataset.originalText || defaultText;
                    }
                }
                return; // do not open modal
            }
            state.ui.currentOpponentId = opponentId;
            state.ui.currentOpponentName = opponentName;
            state.ui.currentButtonElement = buttonElement;
            state.ui.currentSetterType = type;
            const title = type === 'medDeal' ? 'Set Med Deal for' : 'Assign Dibs for';
            const defaultText = type === 'medDeal' ? 'Set Med Deal' : 'Dibs';
            if (state.ui.currentButtonElement) {
                state.ui.currentButtonElement.disabled = true;
                state.ui.currentButtonElement.innerHTML = '<span class="dibs-spinner"></span> Loading...';
            }
            if (!state.ui.setterModal) {
                state.ui.setterModal = utils.createElement('div', { id: 'setter-modal', style: { position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', backgroundColor: '#1a1a1a', border: '1px solid #333', borderRadius: '8px', padding: '20px', zIndex: 10002, boxShadow: '0 4px 8px rgba(0,0,0,0.5)', maxWidth: '400px', width: '90%', color: 'white' } });
                state.ui.setterModal.innerHTML = `
                    <h3 style="margin-top: 0;">${title} ${opponentName}</h3>
                    <input type="text" id="setter-search" placeholder="Search members..." style="width: calc(100% - 10px); padding: 5px; margin-bottom: 10px; background-color: #222; border: 1px solid #555; color: white; border-radius: 4px;">
                    <ul id="setter-list" style="list-style: none; padding: 0; margin: 0; max-height: 200px; overflow-y: auto; border: 1px solid #555; border-radius: 4px;"></ul>
                    <button id="cancel-setter" style="background-color: #f44336; color: white; border: none; border-radius: 4px; padding: 8px 15px; cursor: pointer; margin-top: 10px;">Cancel</button>
                `;
                document.body.appendChild(state.ui.setterModal);
                state.ui.setterSearchInput = document.getElementById('setter-search');
                state.ui.setterList = document.getElementById('setter-list');
                document.getElementById('cancel-setter').onclick = (e) => { e.preventDefault(); e.stopPropagation(); ui.closeSetterModal(); };
                state.ui.setterSearchInput.addEventListener('input', ui.filterSetterList);
            } else {
                state.ui.setterModal.querySelector('h3').textContent = `${title} ${opponentName}`;
                state.ui.setterSearchInput.value = '';
            }
            state.ui.setterList.innerHTML = '<li style="padding: 8px; text-align: center; color: #aaa;"><span class="dibs-spinner"></span> Loading...</li>';
            state.ui.setterSearchInput.disabled = true;
            try {
                ui.populateSetterList();
            } catch (error) {
                console.error(`[TDM] Error populating ${type} setter modal:`, error);
                state.ui.setterList.innerHTML = '<li style="padding: 8px; text-align: center; color: #f44336;">Failed to load members.</li>';
            } finally {
                state.ui.setterSearchInput.disabled = false;
                if (state.ui.currentButtonElement) {
                    state.ui.currentButtonElement.disabled = false;
                    state.ui.currentButtonElement.textContent = buttonElement.dataset.originalText || defaultText;
                }
            }
            state.ui.setterModal.style.display = 'block';
        },

        openDibsSetterModal: (opponentId, opponentName, buttonElement) => ui.openSetterModal(opponentId, opponentName, buttonElement, 'dibs'),
        openMedDealSetterModal: (opponentId, opponentName, buttonElement) => ui.openSetterModal(opponentId, opponentName, buttonElement, 'medDeal'),

        closeSetterModal: () => {
            if (state.ui.setterModal) state.ui.setterModal.style.display = 'none';
            const defaultText = state.ui.currentSetterType === 'medDeal' ? 'Set Med Deal' : 'Dibs';
            if (state.ui.currentButtonElement) {
                state.ui.currentButtonElement.disabled = false;
                state.ui.currentButtonElement.textContent = state.ui.currentButtonElement.dataset.originalText || defaultText;
                state.ui.currentButtonElement = null;
            }
            state.ui.currentOpponentId = null;
            state.ui.currentOpponentName = null;
            state.ui.currentSetterType = null;
            handlers.debouncedFetchGlobalData();
        },

        populateSetterList: () => {
            state.ui.setterList.innerHTML = '';
            const validMembers = state.factionMembers.filter(member => member.id && member.name);
            const sortedMembers = [...validMembers].sort((a, b) => {
                const aId = String(a.id);
                const bId = String(b.id);
                if (aId === state.user.tornId) return -1;
                if (bId === state.user.tornId) return 1;
                return a.name.localeCompare(b.name);
            });
            if (sortedMembers.length === 0) {
                state.ui.setterList.appendChild(utils.createElement('li', { textContent: 'No faction members found.', style: { padding: '8px', color: '#aaa' } }));
                return;
            }
            sortedMembers.forEach(member => {
                const li = utils.createElement('li', {
                    textContent: member.name,
                    dataset: { userId: member.id, username: member.name },
                    style: { padding: '8px', cursor: 'pointer', borderBottom: '1px solid #333', backgroundColor: '#2c2c2c' },
                    onmouseover: () => li.style.backgroundColor = '#444',
                    onmouseout: () => li.style.backgroundColor = '#2c2c2c',
                    onclick: async () => {
                        if (state.ui.currentSetterType === 'medDeal') {
                            await handlers.debouncedHandleMedDealToggle(state.ui.currentOpponentId, state.ui.currentOpponentName, true, member.id, member.name, state.ui.currentButtonElement);
                        } else {
                            await handlers.debouncedAssignDibs(state.ui.currentOpponentId, state.ui.currentOpponentName, member.id, member.name, state.ui.currentButtonElement);
                        }
                        ui.closeSetterModal();
                    }
                });
                state.ui.setterList.appendChild(li);
            });
        },

        filterSetterList: () => {
            const searchTerm = state.ui.setterSearchInput.value.toLowerCase();
            const items = state.ui.setterList.querySelectorAll('li');
            items.forEach(item => {
                const username = item.dataset.username?.toLowerCase() || '';
                item.style.display = username.includes(searchTerm) ? 'block' : 'none';
            });
        },

        showMessageBox: (message, type = 'info', duration = 5000, onClick = null) => {
            const messageBox = utils.createElement('div', {
                className: 'message-box-on-top',
                textContent: message,
                style: { backgroundColor: config.CSS.colors[type] || config.CSS.colors.info, zIndex: 9000000005 }
            });
            let isRemoved = false;
            const removeMessageBox = () => {
                if (isRemoved) return;
                isRemoved = true;
                messageBox.style.opacity = '0';
                setTimeout(() => { if (messageBox.parentNode) messageBox.parentNode.removeChild(messageBox); }, 500);
            };
            messageBox.addEventListener('click', async (e) => {
                e.preventDefault(); e.stopPropagation();
                if (onClick) await onClick();
                removeMessageBox();
            });
            document.body.appendChild(messageBox);
            setTimeout(() => messageBox.style.opacity = '1', 10);
            setTimeout(removeMessageBox, duration);
        },

        showConfirmationBox: (message, showCancel = true) => {
            return new Promise(resolve => {
                const confirmBox = utils.createElement('div', { style: { position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', backgroundColor: '#1a1a1a', border: '1px solid #333', borderRadius: '8px', padding: '20px', zIndex: 10001, boxShadow: '0 4px 8px rgba(0,0,0,0.5)', maxWidth: '350px', width: '90%', color: 'white', textAlign: 'center' } });
                const messagePara = utils.createElement('p', { style: { marginBottom: '20px' }, textContent: message });
                const buttonsContainer = utils.createElement('div', { style: { display: 'flex', justifyContent: 'center' } });
                const okButton = utils.createElement('button', { id: 'confirm-ok', style: { backgroundColor: config.CSS.colors.success, color: 'white', border: 'none', borderRadius: '4px', padding: '8px 15px', cursor: 'pointer', marginRight: showCancel ? '10px' : '0' }, textContent: showCancel ? 'Yes' : 'OK', onclick: () => { confirmBox.remove(); resolve(true); } });
                buttonsContainer.appendChild(okButton);
                if (showCancel) {
                    const cancelButton = utils.createElement('button', { id: 'confirm-cancel', style: { backgroundColor: config.CSS.colors.error, color: 'white', border: 'none', borderRadius: '4px', padding: '8px 15px', cursor: 'pointer' }, textContent: 'No', onclick: () => { confirmBox.remove(); resolve(false); } });
                    buttonsContainer.appendChild(cancelButton);
                }
                confirmBox.appendChild(messagePara);
                confirmBox.appendChild(buttonsContainer);
                document.body.appendChild(confirmBox);
            });
        },

        createSettingsButton: () => {
            if (document.getElementById('tdm-settings-button')) return;
            const topPageLinksList = document.querySelector('#top-page-links-list');
            if (!topPageLinksList) return;

            const settingsButton = utils.createElement('span', { id: 'tdm-settings-button', style: { marginRight: '5px', marginLeft: '10px', cursor: 'pointer', display: 'inline-block', verticalAlign: 'middle' }, innerHTML: `<span style="background: linear-gradient(to bottom, #00b300, #008000); border: 2px solid #ffcc00; border-radius: 4px; box-sizing: border-box; color: #ffffff; text-shadow: 1px 1px 1px #000000; cursor: pointer; display: inline-block; font-family: 'Farfetch Basis', 'Helvetica Neue', Arial, sans-serif; font-size: 12px; font-weight: bold; line-height: 20px; height: 20px; margin: 0; padding: 0 8px; text-align: center; text-transform: none;">TreeDibs</span>`, onclick: ui.toggleSettingsPopup });
            const retalsButton = utils.createElement('span', { id: 'tdm-retals-button', style: { marginRight: '5px', marginLeft: '5px', cursor: 'pointer', display: 'inline-block', verticalAlign: 'middle' }, innerHTML: `<span style="background: linear-gradient(to bottom, #ff5722, #e64a19); border: 2px solid #ffcc00; border-radius: 4px; box-sizing: border-box; color: #ffffff; text-shadow: 1px 1px 1px #000000; cursor: pointer; display: inline-block; font-family: 'Farfetch Basis', 'Helvetica Neue', Arial, sans-serif; font-size: 12px; font-weight: bold; line-height: 20px; height: 20px; margin: 0; padding: 0 8px; text-align: center; text-transform: none;">... Retals</span>`, onclick: () => ui.showAllRetaliationsNotification() });

            if (topPageLinksList.firstChild) {
                topPageLinksList.insertBefore(retalsButton, topPageLinksList.firstChild);
                topPageLinksList.insertBefore(settingsButton, topPageLinksList.firstChild);
            } else {
                topPageLinksList.appendChild(settingsButton);
                topPageLinksList.appendChild(retalsButton);
            }
            ui.updateRetalsButtonCount(); // Call initially to set the count
        },

        updateRetalsButtonCount: () => {
            const retalsButton = document.getElementById('tdm-retals-button');
            if (!retalsButton) return;
            const retalSpan = retalsButton.querySelector('span');
            if (!retalSpan) return;

            const activeRetals = Object.values(state.retaliationOpportunities).filter(opp => opp.timeRemaining > 0).length;
            retalSpan.textContent = `${activeRetals} Retals`;
        },

        toggleSettingsPopup: async () => {
            let settingsPopup = document.getElementById('tdm-settings-popup');
            if (settingsPopup) {
                settingsPopup.remove();
                return;
            }
            const contentTitle = document.querySelector('div.content-title.m-bottom10');
            const contentWrapper = document.querySelector('.content-wrapper');
            if (!contentTitle && !contentWrapper) return;

            // utils.perf.start('toggleSettingsPopup'); // Start timer
            // Refresh latest faction settings for the panel
            try {
                const latest = await api.get('getFactionSettings', { factionId: state.user.factionId });
                if (latest) state.script.factionSettings = latest;
            } catch (e) { /* non-fatal */ }

            settingsPopup = utils.createElement('div', { id: 'tdm-settings-popup', style: { width: '100%', marginBottom: '5px', backgroundColor: '#2c2c2c', border: '1px solid #333', borderRadius: '8px', boxShadow: '0 4px 10px rgba(0,0,0,0.5)', padding: '0', fontFamily: "'Inter', sans-serif", color: '#e0e0e0' } });
            const header = utils.createElement('div', { style: { padding: '10px', backgroundColor: config.CSS.colors.mainColor, borderTopLeftRadius: '8px', borderTopRightRadius: '8px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, innerHTML: `<h3 style="margin: 0; color: white; font-size: 16px;">TreeDibsMapper v${config.VERSION}</h3><span id="tdm-settings-close" style="cursor: pointer; font-size: 18px;">×</span>` });
            const content = utils.createElement('div', { id: 'tdm-settings-content', style: { padding: '5px' } });
            settingsPopup.appendChild(header);
            settingsPopup.appendChild(content);
            header.querySelector('#tdm-settings-close').addEventListener('click', ui.toggleSettingsPopup);

            if (contentTitle) contentTitle.parentNode.insertBefore(settingsPopup, contentTitle.nextSibling);
            else if (contentWrapper) contentWrapper.insertBefore(settingsPopup, contentWrapper.firstChild);
            else document.body.appendChild(settingsPopup);

            ui.updateSettingsContent();

            // utils.perf.stop('toggleSettingsPopup'); // Stop timer
        },

        updateSettingsContent: () => {
            const content = document.getElementById('tdm-settings-content');
            if (!content) return;
            // utils.perf.start('updateSettingsContent');
            const warType = state.warData.warType || 'War Type Not Set';
            const termType = state.warData.termType || 'Set Term Type';
            const scoreCap = state.warData.scoreCap ?? 0;
            const scoreType = state.warData.scoreType || 'Set Score Type';
            const opponentFactionName = state.warData.opponentFactionName || state.lastOpponentFactionName;
            const opponentFactionId = state.warData.opponentFactionId || state.lastOpponentFactionId;
            const termedWarDisplay = warType === 'Termed War' ? 'block' : 'none';

            const ocReminderEnabled = storage.get('ocReminderEnabled', true); // Default to true

            // Determine admin status for settings edits
            const isAdmin = !!state.script.canAdministerMedDeals && (storage.get('adminFunctionality', true) === true);
            const factionSettings = state.script.factionSettings || {};
            const dibsStyle = (factionSettings.options && factionSettings.options.dibsStyle) || {
                keepTillInactive: true,
                mustRedibAfterSuccess: false,
                allowStatuses: { Okay: true, Hospital: true, Travel: false, Abroad: false, Jail: false },
                removeOnFly: false,
                inactivityTimeoutSeconds: 300,
                timeRemainingLimits: { minSecondsToDib: 0 }
            };

            // Build new, compact, collapsible sections
            content.innerHTML = `
                <div class="settings-section collapsible" data-section="latest-war">
                    <div class="settings-header collapsible-header">Latest Ranked War Details <span class="chevron">▾</span></div>
                    <div class="collapsible-content">
                        <div style="margin-top: 4px; padding: 6px; background-color: #222; border-radius: 5px;">
                            <div id="war-type-container" style="margin-bottom: 4px; display: block;">
                                <label style="display: block; margin-bottom: 4px; color: #ccc;">War Type:</label>
                                ${state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true ? `
                                <select id="war-type-select" class="settings-input">
                                    <option value="" disabled ${!warType || warType === 'War Type Not Set' ? 'selected' : ''}>War Type Not Set</option>
                                    <option value="Termed War" ${warType === 'Termed War' ? 'selected' : ''}>Termed War</option>
                                    <option value="Ranked War" ${warType === 'Ranked War' ? 'selected' : ''}>Ranked War</option>
                                </select>
                                ` : `<div class="settings-input-display">${warType}</div>`}
                            </div>
                            <div id="term-type-container" style="margin-bottom: 4px; display: ${termedWarDisplay};">
                                <label style="display: block; margin-bottom: 4px; color: #ccc;">Term Type:</label>
                                ${state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true ? `
                                <select id="term-type-select" class="settings-input">
                                    <option value="Termed Loss" ${termType === 'Termed Loss' ? 'selected' : ''}>Termed Loss</option>
                                    <option value="Termed Win" ${termType === 'Termed Win' ? 'selected' : ''}>Termed Win</option>
                                </select>
                                ` : `<div class="settings-input-display">${termType}</div>`}
                            </div>
                            <div id="score-cap-container" style="margin-bottom: 4px; display: ${termedWarDisplay};">
                                <label style="display: block; margin-bottom: 4px; color: #ccc;">Score Cap:</label>
                                ${state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true ? `
                                <input type="number" id="score-cap-input" value="${scoreCap}" min="0" class="settings-input">
                                ` : `<div class="settings-input-display">${scoreCap}</div>`}
                            </div>
                            <div id="score-type-container" style="margin-bottom: 4px; display: ${termedWarDisplay};">
                                <label style="display: block; margin-bottom: 4px; color: #ccc;">Score Type:</label>
                                ${state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true ? `
                                <select id="score-type-select" class="settings-input">
                                    <option value="Attacks" ${scoreType === 'Attacks' ? 'selected' : ''}>Attacks</option>
                                    <option value="Respect" ${scoreType === 'Respect' ? 'selected' : ''}>Respect</option>
                                    <option value="Respect (no chain)" ${scoreType === 'Respect (no chain)' ? 'selected' : ''}>Respect (no chain)</option>
                                </select>
                                ` : `<div class="settings-input-display">${scoreType}</div>`}
                            </div>
                            <div style="margin-bottom: 4px;">
                                <label style="display: block; margin-bottom: 4px; color: #ccc;">Opponent Faction:</label>
                                <div class="settings-input-display">${opponentFactionName} ${opponentFactionId ? `(ID: ${opponentFactionId})` : ''}</div>
                            </div>
                            <div style="margin-top: 6px; text-align: center;">
                                <button id="save-war-data-btn" class="settings-btn settings-btn-green" style="display: ${storage.get('adminFunctionality', true) ? 'inline-block' : 'none'};">Save War Data</button>
                            </div>

                            <div class="settings-subheader" style="margin-top:8px; margin-bottom:4px; color:#93c5fd; text-align:center;">Dibs Style (Faction)</div>
                            <div style="display:flex; gap:8px; flex-wrap:wrap; width:100%;">
                                <label style="flex:1; min-width:200px; color:#ccc;">Keep Dibs Until Inactive (unchecked = permanent)
                                    <input type="checkbox" id="dibs-keep-inactive" ${dibsStyle.keepTillInactive ? 'checked' : ''} ${isAdmin ? '' : 'disabled'} style="margin-left:6px;" />
                                </label>
                                <label style="flex:1; min-width:220px; color:#ccc;">Inactivity Timeout (seconds)
                                    <input type="number" id="dibs-inactivity-seconds" min="60" step="30" value="${parseInt(dibsStyle.inactivityTimeoutSeconds||300)}" ${isAdmin ? '' : 'disabled'} class="settings-input" />
                                </label>
                                <label style="flex:1; min-width:200px; color:#ccc;">Require Re-dib After Success
                                    <input type="checkbox" id="dibs-redib-after-success" ${dibsStyle.mustRedibAfterSuccess ? 'checked' : ''} ${isAdmin ? '' : 'disabled'} style="margin-left:6px;" />
                                </label>
                            </div>
                            <div style="display:flex; gap:8px; flex-wrap:wrap; width:100%; margin-top:4px;">
                                <div style="flex:1; min-width:260px; color:#ccc;">
                                    Allowed Opponent Statuses:
                                    <div style="display:flex; gap:10px; flex-wrap:wrap; margin-top:6px;">
                                        ${['Okay','Hospital','Travel','Abroad','Jail'].map(s => `
                                            <label style="color:#ccc;">
                                                <input type="checkbox" class="dibs-allow-status" data-status="${s}" ${dibsStyle.allowStatuses?.[s] ? 'checked' : ''} ${isAdmin ? '' : 'disabled'} /> ${s}
                                            </label>
                                        `).join('')}
                                    </div>
                                </div>
                                <div style="flex:1; min-width:260px; color:#ccc;">
                                    Dib Opponent Max Hosp Release Time (minutes):
                                    <div style="margin-top:6px;">
                                        <input type="number" id="dibs-max-hosp-minutes" min="0" step="1" value="${Number(dibsStyle.maxHospitalReleaseMinutes||0)}" ${isAdmin ? '' : 'disabled'} class="settings-input" />
                                        <div style="font-size:11px; color:#aaa; margin-top:2px;">Blank or 0 = no limit. If set (e.g. 5), only allow dib when Hospital release time is under this many minutes.</div>
                                    </div>
                                </div>
                                <div style="flex:1; min-width:260px; color:#ccc;">
                                    Allowed User Statuses:
                                    <div style="display:flex; gap:10px; flex-wrap:wrap; margin-top:6px;">
                                        ${(() => { const dflt = { Okay: true, Hospital: true, Travel: false, Abroad: false, Jail: false }; const aus = dibsStyle.allowedUserStatuses || {}; return ['Okay','Hospital','Travel','Abroad','Jail'].map(s => `
                                            <label style=\"color:#ccc;\"> 
                                                <input type=\"checkbox\" class=\"dibs-allow-user-status\" data-status=\"${s}\" ${(aus[s] ?? dflt[s]) ? 'checked' : ''} ${isAdmin ? '' : 'disabled'} /> ${s}
                                            </label>
                                        `).join(''); })()}
                                    </div>
                                </div>
                                <label style="flex:1; min-width:220px; color:#ccc;">Remove If Opponent Travels
                                    <input type="checkbox" id="dibs-remove-on-fly" ${dibsStyle.removeOnFly ? 'checked' : ''} ${isAdmin ? '' : 'disabled'} style="margin-left:6px;" />
                                </label>
                                <label style="flex:1; min-width:220px; color:#ccc;">Remove If You Travel
                                    <input type="checkbox" id="dibs-remove-user-travel" ${(dibsStyle.removeWhenUserTravels ? 'checked' : '')} ${isAdmin ? '' : 'disabled'} style="margin-left:6px;" />
                                </label>
                            </div>
                            <div style="margin-top:6px; text-align:center;">
                                <button id="save-dibs-style-btn" class="settings-btn settings-btn-green" style="display:${isAdmin ? 'inline-block' : 'none'};">Save Dibs Style</button>
                            </div>
                            ${!isAdmin ? '<div style="text-align:center; color:#aaa; margin-top:4px;">Visible to all members. Only admins can edit.</div>' : ''}
                        </div>
                    </div>
                </div>

                <div class="settings-section settings-section-divided collapsible" data-section="ranked-war-tools">
                    <div class="settings-header collapsible-header">Ranked War Tools <span class="chevron">▾</span></div>
                    <div class="collapsible-content" style="display:flex; gap:6px; align-items:center; flex-wrap:wrap;">
                        <select id="ranked-war-id-select" class="settings-input" style="flex-grow: 1;"><option value="">Loading wars...</option></select>
                        <button id="show-ranked-war-summary-btn" class="settings-btn settings-btn-green">War Summary</button>
                        <button id="view-war-attacks-btn" class="settings-btn settings-btn-blue">War Attacks</button>
                    </div>
                </div>

                <div class="settings-section settings-section-divided collapsible" data-section="column-visibility">
                    <div class="settings-header collapsible-header">Column Visibility <span class="chevron">▾</span></div>
                    <div class="collapsible-content">
                        <div id="column-visibility-buttons" class="settings-button-group"></div>
                    </div>
                </div>

                ${state.script.canAdministerMedDeals && storage.get('adminFunctionality', true) === true ? `
                <div class="settings-section settings-section-divided collapsible" data-section="admin-settings">
                    <div class="settings-header collapsible-header">Admin Settings <span class="chevron">▾</span></div>
                    <div class="collapsible-content">
                        <div class="settings-button-group">
                            <button id="admin-functionality-btn" class="column-toggle-btn ${storage.get('adminFunctionality', true) ? 'active' : 'inactive'}">Manage Others Dibs/Deals</button>
                            ${storage.get('adminFunctionality', true) ? `
                            <button id="view-unauthorized-attacks-btn" class="settings-btn">View Unauthorized Attacks</button>
                            ` : ''}
                        </div>
                    </div>
                </div>` : ''}

                <div class="settings-section settings-section-divided collapsible" data-section="general-settings">
                    <div class="settings-header collapsible-header">Settings <span class="chevron">▾</span></div>
                    <div class="collapsible-content">
                        <div class="settings-button-group">
                            <button id="chain-timer-btn" class="settings-btn ${storage.get('chainTimerEnabled', true) ? 'settings-btn-green' : 'settings-btn-red'}">Chain Timer: ${storage.get('chainTimerEnabled', true) ? 'Enabled' : 'Disabled'}</button>
                            <button id="inactivity-timer-btn" class="settings-btn ${storage.get('inactivityTimerEnabled', true) ? 'settings-btn-green' : 'settings-btn-red'}">Inactivity Timer: ${storage.get('inactivityTimerEnabled', true) ? 'Enabled' : 'Disabled'}</button>
                            <button id="opponent-status-btn" class="settings-btn ${storage.get('opponentStatusTimerEnabled', true) ? 'settings-btn-green' : 'settings-btn-red'}">Opponent Status: ${storage.get('opponentStatusTimerEnabled', true) ? 'Enabled' : 'Disabled'}</button>
                            <button id="api-usage-btn" class="settings-btn ${storage.get('apiUsageCounterEnabled', true) ? 'settings-btn-green' : 'settings-btn-red'}">API Counter: ${storage.get('apiUsageCounterEnabled', true) ? 'Shown' : 'Hidden'}</button>
                            <button id="oc-reminder-btn" class="settings-btn ${ocReminderEnabled ? 'settings-btn-green' : 'settings-btn-red'}">OC Reminder: ${ocReminderEnabled ? 'Enabled' : 'Disabled'}</button>
                            <button id="reset-settings-btn" class="settings-btn settings-btn-red">Reset All Settings</button>
                            <button id="tdm-adoption-btn" class="settings-btn settings-btn-blue">TDM Adoption Info</button>
                        </div>
                    </div>
                </div>`;

            // Re-attach all event listeners
            document.getElementById('war-type-select')?.addEventListener('change', (e) => {
                const isTermed = e.currentTarget.value === 'Termed War';
                document.getElementById('term-type-container').style.display = isTermed ? 'block' : 'none';
                document.getElementById('score-cap-container').style.display = isTermed ? 'block' : 'none';
                document.getElementById('score-type-container').style.display = isTermed ? 'block' : 'none';
            });
            document.getElementById('save-war-data-btn')?.addEventListener('click', async (e) => {
                const warDataToSave = { ...state.warData };
                warDataToSave.warType = document.getElementById('war-type-select').value;
                if (warDataToSave.warType === 'Termed War') {
                    warDataToSave.termType = document.getElementById('term-type-select').value;
                    warDataToSave.scoreCap = parseInt(document.getElementById('score-cap-input').value) || 0;
                    warDataToSave.scoreType = document.getElementById('score-type-select').value;
                }
                await handlers.debouncedSetFactionWarData(warDataToSave, e.currentTarget);
            });
            const visibilityButtonsContainer = document.getElementById('column-visibility-buttons');
            if (visibilityButtonsContainer) {
                const rankedWarHeaderId = 'ranked-war-header';
                const membersListHeaderId = 'members-list-header';
                const rankedWarColumns = [
                    { key: 'lvl', label: 'Level' },
                    { key: 'factionIcon', label: 'Faction Icon' }
                ];
                const membersListColumns = [
                    { key: 'lvl', label: 'Level' },
                    { key: 'memberIcons', label: 'Member Icons' },
                    { key: 'position', label: 'Position' },
                    { key: 'days', label: 'Days' },
                    { key: 'factionIcon', label: 'Faction Icon' },
                    { key: 'dibsDeals', label: 'Dibs/Med Deals' },
                    { key: 'notes', label: 'Notes' }
                ];

                // Ranked War Header
                if (!document.getElementById(rankedWarHeaderId)) {
                    const rankedWarHeader = utils.createElement('div', { id: rankedWarHeaderId, textContent: 'Ranked War Table Columns', className: 'settings-header', style: { marginTop: '4px' } });
                    visibilityButtonsContainer.appendChild(rankedWarHeader);
                }
                // Ranked War Buttons
                rankedWarColumns.forEach(col => {
                    if (!visibilityButtonsContainer.querySelector(`button[data-column="${col.key}"][data-table="rankedWar"]`)) {
                        const vis = storage.get('columnVisibility', config.DEFAULT_COLUMN_VISIBILITY);
                        const active = vis.rankedWar?.[col.key] !== false ? 'active' : 'inactive';
                        const button = utils.createElement('button', {
                            className: `column-toggle-btn ${active}`,
                            dataset: { column: col.key, table: 'rankedWar' },
                            textContent: col.label,
                            onclick: () => {
                                const vis = storage.get('columnVisibility', config.DEFAULT_COLUMN_VISIBILITY);
                                if (!vis.rankedWar) vis.rankedWar = {};
                                vis.rankedWar[col.key] = !vis.rankedWar[col.key];
                                storage.set('columnVisibility', vis);
                                ui.updateColumnVisibilityStyles();
                                ui.updateSettingsContent();
                            }
                        });
                        visibilityButtonsContainer.appendChild(button);
                    }
                });

                // Members List Header
                if (!document.getElementById(membersListHeaderId)) {
                    const membersListHeader = utils.createElement('div', { id: membersListHeaderId, textContent: 'Members List Table Columns', className: 'settings-header', style: { marginTop: '4px' } });
                    visibilityButtonsContainer.appendChild(membersListHeader);
                }
                // Members List Buttons
                membersListColumns.forEach(col => {
                    if (!visibilityButtonsContainer.querySelector(`button[data-column="${col.key}"][data-table="membersList"]`)) {
                        const vis = storage.get('columnVisibility', config.DEFAULT_COLUMN_VISIBILITY);
                        const active = vis.membersList?.[col.key] !== false ? 'active' : 'inactive';
                        const button = utils.createElement('button', {
                            className: `column-toggle-btn ${active}`,
                            dataset: { column: col.key, table: 'membersList' },
                            textContent: col.label,
                            onclick: () => {
                                const vis = storage.get('columnVisibility', config.DEFAULT_COLUMN_VISIBILITY);
                                if (!vis.membersList) vis.membersList = {};
                                vis.membersList[col.key] = !vis.membersList[col.key];
                                storage.set('columnVisibility', vis);
                                ui.updateColumnVisibilityStyles();
                                ui.updateSettingsContent();
                            }
                        });
                        visibilityButtonsContainer.appendChild(button);
                    }
                });
            }
            document.getElementById('admin-functionality-btn')?.addEventListener('click', () => {
                storage.set('adminFunctionality', !storage.get('adminFunctionality', true));
                ui.updateSettingsContent();
            });
            document.getElementById('view-unauthorized-attacks-btn')?.addEventListener('click', () => ui.showUnauthorizedAttacksModal());
            // Collapsible toggles
            document.querySelectorAll('#tdm-settings-content .collapsible-header').forEach(h => {
                h.addEventListener('click', () => {
                    const sec = h.closest('.collapsible');
                    if (!sec) return;
                    sec.classList.toggle('collapsed');
                });
            });
            // Attach event listeners
            document.getElementById('chain-timer-btn')?.addEventListener('click', () => {
                const cur = storage.get('chainTimerEnabled', true);
                storage.set('chainTimerEnabled', !cur);
                if (!cur) ui.ensureChainTimer(); else ui.removeChainTimer();
                ui.updateSettingsContent();
            });
            document.getElementById('inactivity-timer-btn')?.addEventListener('click', () => {
                const cur = storage.get('inactivityTimerEnabled', true);
                storage.set('inactivityTimerEnabled', !cur);
                if (!cur) ui.ensureInactivityTimer(); else ui.removeInactivityTimer();
                ui.updateSettingsContent();
            });
            document.getElementById('opponent-status-btn')?.addEventListener('click', () => {
                const cur = storage.get('opponentStatusTimerEnabled', true);
                storage.set('opponentStatusTimerEnabled', !cur);
                if (!cur) ui.ensureOpponentStatus(); else ui.removeOpponentStatus();
                ui.updateSettingsContent();
            });
            document.getElementById('api-usage-btn')?.addEventListener('click', () => {
                const cur = storage.get('apiUsageCounterEnabled', true);
                storage.set('apiUsageCounterEnabled', !cur);
                if (!cur) ui.ensureApiUsageBadge();
                ui.updateApiUsageBadge();
                ui.updateSettingsContent();
            });
            document.getElementById('oc-reminder-btn')?.addEventListener('click', () => {
                const currentState = storage.get('ocReminderEnabled', true);
                storage.set('ocReminderEnabled', !currentState);
                ui.updateSettingsContent(); // Re-render settings
            });
            document.getElementById('reset-settings-btn')?.addEventListener('click', async () => {
                if (await ui.showConfirmationBox('Are you sure you want to reset all settings?')) {
                    Object.keys(localStorage).filter(k => k.startsWith('tdm_')).forEach(k => localStorage.removeItem(k));
                    ui.showMessageBox('Settings reset. Reloading...', 'info');
                    setTimeout(() => location.reload(), 1500);
                }
            });
            document.getElementById('tdm-adoption-btn')?.addEventListener('click', ui.showTDMAdoptionModal);

            // Dibs Style save
            const saveDibsBtn = document.getElementById('save-dibs-style-btn');
            if (saveDibsBtn) {
                saveDibsBtn.addEventListener('click', async (e) => {
                    const btn = e.currentTarget;
                    btn.disabled = true; btn.innerHTML = '<span class="dibs-spinner"></span> Saving...';
                    try {
                        const allowStatuses = {};
                        document.querySelectorAll('.dibs-allow-status').forEach(cb => {
                            allowStatuses[cb.dataset.status] = cb.checked;
                        });
                        const allowedUserStatuses = {};
                        document.querySelectorAll('.dibs-allow-user-status').forEach(cb => {
                            allowedUserStatuses[cb.dataset.status] = cb.checked;
                        });
                        const maxHospMinsRaw = document.getElementById('dibs-max-hosp-minutes')?.value;
                        const maxHospMins = maxHospMinsRaw === '' ? 0 : Math.max(0, parseInt(maxHospMinsRaw));
                        const payload = {
                            options: {
                                dibsStyle: {
                                    keepTillInactive: document.getElementById('dibs-keep-inactive').checked,
                                    mustRedibAfterSuccess: document.getElementById('dibs-redib-after-success').checked,
                                    removeOnFly: document.getElementById('dibs-remove-on-fly').checked,
                                    removeWhenUserTravels: document.getElementById('dibs-remove-user-travel')?.checked || false,
                                    inactivityTimeoutSeconds: parseInt(document.getElementById('dibs-inactivity-seconds').value) || 300,
                                    maxHospitalReleaseMinutes: maxHospMins,
                                    allowStatuses,
                                    allowedUserStatuses
                                }
                            }
                        };
                        const res = await api.post('updateFactionSettings', { factionId: state.user.factionId, ...payload });
                        state.script.factionSettings = res?.settings || state.script.factionSettings;
                        ui.showMessageBox('Dibs Style saved.', 'success');
                    } catch (err) {
                        ui.showMessageBox(`Failed to save Dibs Style: ${err.message || 'Unknown error'}`, 'error');
                    } finally {
                        btn.disabled = false; btn.textContent = 'Save Dibs Style';
                        ui.updateSettingsContent();
                    }
                });
            }
            
            const rankedWarSelect = document.getElementById('ranked-war-id-select');
            const showRankedWarSummaryBtn = document.getElementById('show-ranked-war-summary-btn');
            const viewWarAttacksBtn = document.getElementById('view-war-attacks-btn');

            if (rankedWarSelect && showRankedWarSummaryBtn && viewWarAttacksBtn) {
                showRankedWarSummaryBtn.disabled = true;
                viewWarAttacksBtn.disabled = true;

                (async () => {
                    try {
                        const rankedWars = state.rankWars.length > 0 ? state.rankWars : await api.get('getRankedWars', { factionId: state.user.factionId });
                        state.rankWars = rankedWars;
                        if (Array.isArray(rankedWars) && rankedWars.length > 0) {
                            rankedWarSelect.innerHTML = '';
                            rankedWars.forEach(war => {
                                if (war && war.id && war.factions) {
                                    const opponentFaction = Object.values(war.factions).find(f => f.id !== parseInt(state.user.factionId));
                                    const opponentName = opponentFaction ? opponentFaction.name : 'Unknown';
                                    const option = utils.createElement('option', { value: war.id, textContent: `${war.id} - ${opponentName}` });
                                    rankedWarSelect.appendChild(option);
                                }
                            });
                            showRankedWarSummaryBtn.disabled = false;
                            viewWarAttacksBtn.disabled = false;
                        } else {
                            rankedWarSelect.innerHTML = '<option value="">No ranked wars found</option>';
                        }
                    } catch (error) {
                        console.error("[TDM] Error loading ranked wars into settings panel:", error);
                        rankedWarSelect.innerHTML = '<option value="">Error loading wars</option>';
                    }
                })();

                showRankedWarSummaryBtn.addEventListener('click', async () => {
                    const selectedWarId = rankedWarSelect.value;
                    if (!selectedWarId) { ui.showMessageBox('Please select a ranked war first', 'error'); return; }

                    showRankedWarSummaryBtn.disabled = true;
                    showRankedWarSummaryBtn.innerHTML = '<span class="dibs-spinner"></span>';

                    try {
                        // This single GET call now updates and retrieves the summary from the backend
                        const summary = await api.get('rankedWarSummary', { rankedWarId: selectedWarId, factionId: state.user.factionId });
                        ui.showRankedWarSummaryModal(summary, selectedWarId);
                    } catch (error) {
                        ui.showMessageBox(`Error fetching war summary: ${error.message || 'Unknown error'}`, 'error');
                        console.error("[TDM] War Summary Error:", error);
                    } finally {
                        showRankedWarSummaryBtn.disabled = false;
                        showRankedWarSummaryBtn.textContent = 'War Summary';
                    }
                });

                viewWarAttacksBtn.addEventListener('click', async () => {
                    const selectedWarId = rankedWarSelect.value;
                    if (!selectedWarId) { ui.showMessageBox('Please select a ranked war first', 'error'); return; }

                    viewWarAttacksBtn.disabled = true;
                    viewWarAttacksBtn.innerHTML = '<span class="dibs-spinner"></span>';
                    try {
                        // First, tell the backend to update the raw attack logs from the API
                        await api.post('updateRankedWarAttacks', { rankedWarId: selectedWarId, factionId: state.user.factionId });
                        // Then, show the modal which reads that raw data from Firestore
                        ui.showCurrentWarAttacksModal(selectedWarId);
                    } catch (error) {
                        ui.showMessageBox(`Error preparing war attacks: ${error.message || 'Unknown error'}`, 'error');
                        console.error("[TDM] War Attacks Error:", error);
                    } finally {
                        viewWarAttacksBtn.disabled = false;
                        viewWarAttacksBtn.textContent = 'War Attacks';
                    }
                });
            }
            // utils.perf.stop('updateSettingsContent');
        },

        applyGeneralStyles: () => {
            if (document.getElementById('dibs-general-styles')) return;
            const styleTag = utils.createElement('style', {
                type: 'text/css',
                id: 'dibs-general-styles',
                textContent: `
                    /* --- Main Controls Container --- */
                    .tdm-controls-container {
                        min-width: 120px;
                        max-width: 120px;
                        padding: 2px !important;
                        display: flex;
                        gap: 2px;
                        flex-direction: row;
                    }
                    /* Make the notes column fill the space on our own faction page */
                    .f-war-list .table-body > li:has(.dibs-cell[style*="display: none"]) .notes-cell {
                        width: 100%;
                    }
                    /* Header for the controls column */
                    #col-header-dibs-notes {
                        min-width: 120px;
                        max-width: 120px;
                    }

                    /* --- Individual Cell Styling (Dibs/Notes) --- */
                    .dibs-cell, .notes-cell {
                        flex: 1; /* Make cells share space equally */
                        display: flex;
                        flex-direction: column;
                        gap: 1px; /* MODIFIED: Reduced gap for tighter fit */
                        min-width: 0; /* Important for flex-shrinking */
                    }

                    /* --- Button Styling --- */
                    .tdm-controls-container .btn {
                        flex: 1; /* Make buttons share vertical space */
                        min-height: 0; /* Allows buttons to shrink */
                        padding: 0 4px !important; /* MODIFIED: Removed vertical padding */
                        font-size: 0.8em !important;
                        white-space: nowrap;
                        overflow: hidden;
                        text-overflow: ellipsis;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        line-height: 1.1; /* MODIFIED: Reduced line height */
                        border-radius: 3px; /* Added for consistency */
                    }
                    .tdm-controls-container .med-deal-button {
                        white-space: normal; /* Allow med deal text to wrap */
                    }
                    .tdm-controls-container .dibs-cell:has(.med-deal-button[style*="display: none"]) .dibs-button {
                        flex: 2; /* Make dibs button fill the whole cell if no med deal */
                        height: 100%;
                    }

                    /* --- Other Styles --- */
                    .dibs-spinner { border: 2px solid rgba(255, 255, 255, 0.3); border-top: 2px solid #fff; border-radius: 50%; width: 12px; height: 12px; animation: spin 1s linear infinite; display: inline-block; vertical-align: middle; }
                    @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
                    .message-box-on-top { position: fixed; top: 10px; left: 50%; transform: translateX(-50%); background-color: rgba(0, 0, 0, 0.8); color: white; border-radius: 4px; z-index: 10000; padding: 5px 10px; font-family: 'Inter', sans-serif; font-size: 14px; display: flex; align-items: center; cursor: pointer; }
                    .dibs-cell, .notes-cell, #col-header-dibs, #col-header-notes, .notes-container { display: flex; flex-direction: column; justify-content: center; align-items: stretch; gap: 0px; padding: 0px !important; box-sizing: border-box; height: 100% !important; }
                    .dibs-cell button, .dibs-cell .dibs-button, .dibs-cell .btn-med-deal, #col-header-dibs button, .notes-cell button, .notes-container button, .notes-container .btn, #col-header-notes button { flex: 0 0 48%; width: 100% !important; height: 48% !important; margin: 1%; box-sizing: border-box; font-size: 0.85em !important; align-items: center !important; justify-content: center !important; }
                    .dibs-cell .dibs-button:only-child, .dibs-cell-full .btn-med-deal:only-child, .inactive-note-button:only-child, .active-note-button:only-child, .btn-med-deal-inactive:only-child, .notes-cell .btn:only-child, .notes-cell .button:only-child, .notes-container .btn:only-child, .notes-container .button:only-child { flex: 0 0 100%; margin: 0; width: 100% !important; height: 100% !important; }


                    /* Button Colors */
                    .btn-dibs-inactive { background-color: ${config.CSS.colors.dibsInactive} !important; color: #fff !important; }
                    .btn-dibs-inactive:hover { background-color: ${config.CSS.colors.dibsInactiveHover} !important; }
                    .btn-dibs-success-you { background-color: ${config.CSS.colors.dibsSuccess} !important; color: #fff !important; }
                    .btn-dibs-success-you:hover { background-color: ${config.CSS.colors.dibsSuccessHover} !important; }
                    .btn-dibs-success-other { background-color: ${config.CSS.colors.dibsOther} !important; color: #fff !important; }
                    .btn-dibs-success-other:hover { background-color: ${config.CSS.colors.dibsOtherHover} !important; }
                    .inactive-note-button, .note-button { background-color: ${config.CSS.colors.noteInactive} !important; color: #fff !important; }
                    .inactive-note-button:hover, .note-button:hover { background-color: ${config.CSS.colors.noteInactiveHover} !important; }
                    .active-note-button { background-color: ${config.CSS.colors.noteActive} !important; color: #fff !important; }
                    .active-note-button:hover { background-color: ${config.CSS.colors.noteActiveHover} !important; }
                    .btn-med-deal-inactive, .btn-med-deal-default { background-color: ${config.CSS.colors.medDealInactive} !important; color: #fff !important; }
                    .btn-med-deal-inactive:hover, .btn-med-deal-default:hover { background-color: ${config.CSS.colors.medDealInactiveHover} !important; }
                    .btn-med-deal-set { background-color: ${config.CSS.colors.medDealSet} !important; color: #fff !important; }
                    .btn-med-deal-set:hover { background-color: ${config.CSS.colors.medDealSetHover} !important; }
                    .btn-med-deal-mine { background-color: ${config.CSS.colors.medDealMine} !important; color: #fff !important; }
                    .btn-med-deal-mine:hover { background-color: ${config.CSS.colors.medDealMineHover} !important; }
                    .req-assist-button { background-color: ${config.CSS.colors.assistButton} !important; color: #fff !important; }
                    .req-assist-button:hover { background-color: ${config.CSS.colors.assistButtonHover} !important; }
                    .btn-retal-inactive { background-color: #555555 !important; color: #ccc !important; }
                    .btn-retal-inactive:hover { background-color: #444444 !important; color: #ddd !important; }
                    .btn-retal-active { background-color: #ff5722 !important; color: #fff !important; }
                    .btn-retal-active:hover { background-color: #e64a19 !important; color: #fff !important; }
                    .btn-retal-expired { background-color: #795548 !important; color: #fff !important; }
                    .btn-retal-expired:hover { background-color: #5d4037 !important; color: #fff !important; }
                    .dibs-notes-subrow { width: inherit !important; flex-basis: 100%; order: 100; display: flex; gap: 4px; margin-top: 1px; margin-bottom: 1px; justify-content: flex-start; background: transparent; border: none; box-sizing: border-box; position: relative; }
                    .members-list > li { flex-wrap: wrap !important; width: inherit !important; }
                    .dibs-notes-subrow .btn { min-width: 70px; max-width: 70px; max-height: 30px; font-size: 0.75em; padding: 1px 3px; border-radius: 3px; margin-left: 0; margin-right: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

                    /* Settings Panel Styles (Compacted) */
                    .settings-section { margin-bottom: 4px; }
                    .settings-section-divided { padding-top: 6px; border-top: 1px solid #374151; display: flex; gap: 6px; justify-content: center; align-items: center; flex-wrap: wrap; }
                    .settings-header { font-size: 12px; font-weight: 600; margin: 0 0 6px 0; text-align: center; color: #93c5fd; background-color: #1a1a1a; padding: 4px; border-radius: 4px; width: 100%; user-select: none; }
                    .settings-subheader { font-size: 12px; font-weight: 600; }
                    .settings-button-group { display: flex; flex-wrap: wrap; gap: 4px; justify-content: center; width: 100%; }
                    .settings-input, .settings-input-display { width: 100%; padding: 4px; background-color: #333; color: white; border: 1px solid #555; border-radius: 4px; box-sizing: border-box; }
                    .settings-input-display { padding: 6px 4px; }
                    .settings-btn { background-color: #4b5563; color: #eee; border: 1px solid #6b7280; padding: 5px 10px; font-size: 0.85em; border-radius: 5px; cursor: pointer; transition: background-color 0.2s; }
                    .settings-btn:hover { background-color: #606d7a; }
                    .settings-btn-green { background-color: #4CAF50; border-color: #4CAF50; }
                    .settings-btn-green:hover { background-color: #45a049; }
                    .settings-btn-red { background-color: #f44336; border-color: #f44336; }
                    .settings-btn-red:hover { background-color: #e53935; }
                    .war-type-controls { display: flex; gap: 8px; justify-content: center; margin-bottom: 15px; }
                    .war-type-controls .settings-btn { flex: 1; }
                    .column-toggle-btn { padding: 8px 16px; border: 2px solid #555; border-radius: 6px; background: #2c2c2c; color: #ccc; cursor: pointer; transition: all 0.3s ease; font-size: 0.9em; font-weight: 500; min-width: 100px; text-align: center; }
                    .column-toggle-btn.active { background: #4CAF50; border-color: #4CAF50; color: white; }
                    .column-toggle-btn.active:hover { background: #45a049; border-color: #45a049; color: white; }
                    .column-toggle-btn.inactive { background: #f44336; border-color: #f44336; color: white; }

                    /* Collapsible sections */
                    .collapsible .collapsible-header { cursor: pointer; position: relative; }
                    .collapsible .chevron { float: right; font-size: 12px; opacity: 0.8; }
                    .collapsible.collapsed .collapsible-content { display: none !important; }
                    .collapsible.collapsed .chevron { transform: rotate(-90deg); }

                    /* Text halo for timers */
                    .tdm-text-halo, .tdm-text-halo a { 
                        text-shadow: 
                            -1px -1px 0 #000,
                            1px -1px 0 #000,
                            -1px 1px 0 #000,
                            1px 1px 0 #000,
                            0 0 3px #000;
                    }
                    .tdm-halo-link { color: inherit; text-decoration: underline; cursor: pointer; }
                `
            });
            document.head.appendChild(styleTag);
        },

        updateColumnVisibilityStyles: () => {
            let styleTag = document.getElementById('dibs-column-visibility-dynamic-style');
            if (!styleTag) {
                styleTag = utils.createElement('style', { id: 'dibs-column-visibility-dynamic-style' });
                document.head.appendChild(styleTag);
            }
            let css = '';
            // Update column visibility for both tables using table-specific keys
            const vis = storage.get('columnVisibility', config.DEFAULT_COLUMN_VISIBILITY);
            // Members List selectors
            const membersSelectors = {
                lvl: ['.f-war-list .table-header .lvl.torn-divider.divider-vertical', '.f-war-list .table-body .lvl', '.f-war-list .table-header .level', '.f-war-list .table-body .level'],
                memberIcons: ['.f-war-list .table-header .member-icons.torn-divider.divider-vertical', '.f-war-list .table-body .member-icons'],
                position: ['.f-war-list .table-header .position', '.f-war-list .table-body .position'],
                days: ['.f-war-list .table-header .days', '.f-war-list .table-body .days'],
                factionIcon: ['.f-war-list .table-header .factionWrap___GhZMa', '.f-war-list .table-body .factionWrap___GhZMa', '.table-row .factionWrap___GhZMa'],
                dibsDeals: ['.f-war-list .table-header #col-header-dibs', '.f-war-list .table-body .dibs-cell', '.f-war-list .table-header #col-header-med-deal', '.f-war-list .table-body .med-deal-button'],
                notes: ['.f-war-list .table-header #col-header-notes', '.f-war-list .table-body .notes-cell']
            };
            // Ranked War selectors
            const rankedWarSelectors = {
                lvl: ['.faction-war .table-header .level', '.faction-war .table-body .level'],
                factionIcon: ['.faction-war .factionWrap___GhZMa']
            };

            // Hide columns for Members List
            for (const colName in membersSelectors) {
                if (vis.membersList?.[colName] === false) {
                    const selectors = membersSelectors[colName];
                    if (selectors) css += `${selectors.join(', ')} { display: none !important; }\n`;
                }
            }
            // Hide columns for Ranked War
            for (const colName in rankedWarSelectors) {
                if (vis.rankedWar?.[colName] === false) {
                    const selectors = rankedWarSelectors[colName];
                    if (selectors) css += `${selectors.join(', ')} { display: none !important; }\n`;
                }
            }
            styleTag.textContent = css;
        },

        showCurrentWarAttacksModal: async function(warId) {
            let modal = document.getElementById('current-war-attacks-modal');
            if (!modal) {
                modal = utils.createElement('div', {
                    id: 'current-war-attacks-modal',
                    style: {
                        position: 'fixed',
                        top: '50%',
                        left: '50%',
                        transform: 'translate(-50%, -50%)',
                        backgroundColor: config.CSS.colors.modalBg,
                        border: `1px solid ${config.CSS.colors.modalBorder}`,
                        borderRadius: '8px',
                        padding: '20px',
                        zIndex: 10002,
                        boxShadow: '0 4px 8px rgba(0,0,0,0.5)',
                        maxWidth: '90%',
                        width: '1000px',
                        color: 'white',
                        maxHeight: '80vh',
                        overflowY: 'auto'
                    }
                });
                document.body.appendChild(modal);
            }
            modal.innerHTML = `
                <h3 style="margin-top: 0; text-align: center;">War Attacks (ID: ${warId})</h3>
                <p style="text-align: center; margin-bottom: 7px;"><span class="dibs-spinner"></span> Loading war attacks...</p>
            `;
            modal.style.display = 'block';

            try {
                const allAttacks = await api.get('getRankedWarAttacksFromFirestore', { rankedWarId: warId, factionId: state.user.factionId });

                if (!allAttacks || allAttacks.length === 0) {
                    modal.innerHTML = `
                        <h3 style="margin-top: 0; text-align: center;">War Attacks (ID: ${warId})</h3>
                        <p>No attacks found for this war.</p>
                        <div style="text-align:center;"><button id="close-attacks-modal" class="settings-btn settings-btn-red">Close</button></div>
                    `;
                    modal.querySelector('#close-attacks-modal').addEventListener('click', () => { modal.style.display = 'none'; });
                    return;
                }

                // --- Sorting, Filtering, Records per page ---
                let currentPage = 1;
                let attacksPerPage = 50;
                let sortColumn = 'attackTime';
                let sortDirection = 'desc';
                let filterAttacker = '';
                let filterDefender = '';

                // Unique attacker/defender names
                const uniqueAttackers = [...new Set(allAttacks.map(a => a.attacker?.name).filter(Boolean))].sort();
                const uniqueDefenders = [...new Set(allAttacks.map(a => a.defender?.name).filter(Boolean))].sort();

                function getFilteredSortedAttacks() {
                    let filtered = allAttacks.filter(a => {
                        let attackerMatch = !filterAttacker || a.attacker?.name === filterAttacker;
                        let defenderMatch = !filterDefender || a.defender?.name === filterDefender;
                        return attackerMatch && defenderMatch;
                    });
                    filtered = filtered.slice(); // shallow copy
                    filtered.sort((a, b) => {
                        let valA, valB;
                        switch (sortColumn) {
                            case 'attackTime':
                                valA = (a.ended || a.started) || 0;
                                valB = (b.ended || b.started) || 0;
                                break;
                            case 'attacker':
                                valA = a.attacker?.name || '';
                                valB = b.attacker?.name || '';
                                break;
                            case 'defender':
                                valA = a.defender?.name || '';
                                valB = b.defender?.name || '';
                                break;
                            case 'result':
                                valA = a.result || '';
                                valB = b.result || '';
                                break;
                            default:
                                valA = (a.ended || a.started) || 0;
                                valB = (b.ended || b.started) || 0;
                        }
                        if (valA < valB) return sortDirection === 'asc' ? -1 : 1;
                        if (valA > valB) return sortDirection === 'asc' ? 1 : -1;
                        return 0;
                    });
                    return filtered;
                }

                function renderPage(page) {
                    currentPage = page;
                    const filteredAttacks = getFilteredSortedAttacks();
                    const totalPages = Math.max(1, Math.ceil(filteredAttacks.length / attacksPerPage));
                    const start = (currentPage - 1) * attacksPerPage;
                    const end = start + attacksPerPage;
                    const pageAttacks = filteredAttacks.slice(start, end);

                    // --- Controls ---
                    let controlsHTML = `
                        <div style="display: flex; flex-wrap: wrap; gap: 10px; justify-content: space-between; align-items: center; margin-bottom: 10px;">
                            <div>
                                <label for="sort-column">Sort by: </label>
                                <select id="sort-column" class="settings-input" style="width: auto;">
                                    <option value="attackTime" ${sortColumn === 'attackTime' ? 'selected' : ''}>Time</option>
                                    <option value="attacker" ${sortColumn === 'attacker' ? 'selected' : ''}>Attacker</option>
                                    <option value="defender" ${sortColumn === 'defender' ? 'selected' : ''}>Defender</option>
                                    <option value="result" ${sortColumn === 'result' ? 'selected' : ''}>Result</option>
                                </select>
                                <select id="sort-direction" class="settings-input" style="width: auto;">
                                    <option value="desc" ${sortDirection === 'desc' ? 'selected' : ''}>Descending</option>
                                    <option value="asc" ${sortDirection === 'asc' ? 'selected' : ''}>Ascending</option>
                                </select>
                            </div>
                            <div>
                                <label for="attacks-per-page">Records per page: </label>
                                <input id="attacks-per-page" type="number" min="1" max="5000" value="${attacksPerPage}" style="width: 60px;" class="settings-input">
                            </div>
                            <div>
                                <label for="filter-attacker">Attacker: </label>
                                <select id="filter-attacker" class="settings-input" style="width: auto;">
                                    <option value="">All</option>
                                    ${uniqueAttackers.map(n => `<option value="${n}" ${filterAttacker === n ? 'selected' : ''}>${n}</option>`).join('')}
                                </select>
                                <label for="filter-defender">Defender: </label>
                                <select id="filter-defender" class="settings-input" style="width: auto;">
                                    <option value="">All</option>
                                    ${uniqueDefenders.map(n => `<option value="${n}" ${filterDefender === n ? 'selected' : ''}>${n}</option>`).join('')}
                                </select>
                            </div>
                        </div>
                    `;

                    // --- Table ---
                    let tableHTML = `<table class="summary-table" style="width: 100%; border-collapse: collapse;">
                        <thead><tr>
                            <th>Time</th>
                            <th>Attacker</th>
                            <th>Defender</th>
                            <th>Result</th>
                            <th>Log</th>
                        </tr></thead>
                        <tbody>`;

                    pageAttacks.forEach(attack => {
                        const ourId = state.user.factionId;
                        const attackerIsOurs = attack.attacker?.faction?.id?.toString() === ourId;
                        const defenderIsOurs = attack.defender?.faction?.id?.toString() === ourId;
                        const attackTime = new Date((attack.ended || attack.started) * 1000).toLocaleString();
                        tableHTML += `
                            <tr style="background-color: #222; color: #c9c9c9ff;">
                                <td style="color: #ffffff;">${attackTime}</td>
                                <td><a href="/profiles.php?XID=${attack.attacker.id}" target="_blank" style="color: ${attackerIsOurs ? '#007204ff' : '#ff0000ff'}; font-weight: bold; text-decoration: underline;">${attack.attacker.name}</a></td>
                                <td><a href="/profiles.php?XID=${attack.defender.id}" target="_blank" style="color: ${defenderIsOurs ? '#007204ff' : '#ff0000ff'}; font-weight: bold; text-decoration: underline;">${attack.defender.name}</a></td>
                                <td style="color: #ffffff;">${attack.result || 'Unknown'}</td>
                                <td style="color: #ffffff;">${attack.code ? `<a href=\"https://www.torn.com/loader.php?sid=attackLog&ID=${attack.code}\" target=\"_blank\">View</a>` : 'N/A'}</td>
                            </tr>`;
                    });
                    tableHTML += `</tbody></table>`;

                    // --- Pagination ---
                    const paginationHTML = `
                        <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 15px;">
                            <button id="prev-page-btn" class="settings-btn" ${currentPage === 1 ? 'disabled' : ''}>Previous</button>
                            <span>Page ${currentPage} of ${totalPages} (${filteredAttacks.length} total)</span>
                            <button id="next-page-btn" class="settings-btn" ${currentPage >= totalPages ? 'disabled' : ''}>Next</button>
                        </div>`;

                    modal.innerHTML = `
                        <h3 style="margin-top: 0; text-align: center;">War Attacks (ID: ${warId})</h3>
                        ${controlsHTML}
                        <div id="attacks-table-container">${tableHTML}</div>
                        ${paginationHTML}
                        <div style="text-align: center; margin-top: 15px;">
                            <button id="close-attacks-modal" class="settings-btn settings-btn-red">Close</button>
                        </div>
                    `;

                    // --- Event Listeners ---
                    modal.querySelector('#close-attacks-modal').addEventListener('click', () => { modal.style.display = 'none'; });
                    modal.querySelector('#prev-page-btn').addEventListener('click', () => renderPage(currentPage - 1));
                    modal.querySelector('#next-page-btn').addEventListener('click', () => renderPage(currentPage + 1));
                    modal.querySelector('#sort-column').addEventListener('change', e => {
                        sortColumn = e.target.value;
                        renderPage(1);
                    });
                    modal.querySelector('#sort-direction').addEventListener('change', e => {
                        sortDirection = e.target.value;
                        renderPage(1);
                    });
                    modal.querySelector('#attacks-per-page').addEventListener('change', e => {
                        let val = parseInt(e.target.value) || 1;
                        attacksPerPage = Math.max(1, Math.min(500, val));
                        renderPage(1);
                    });
                    modal.querySelector('#filter-attacker').addEventListener('change', e => {
                        filterAttacker = e.target.value;
                        renderPage(1);
                    });
                    modal.querySelector('#filter-defender').addEventListener('change', e => {
                        filterDefender = e.target.value;
                        renderPage(1);
                    });
                }

                renderPage(1);

            } catch (e) {
                modal.innerHTML = `
                    <h3 style="margin-top: 0; text-align: center;">War Attacks (ID: ${warId})</h3>
                    <p style="color:${config.CSS.colors.error};">Error loading attacks: ${e.message}</p>
                    <div style="text-align:center;"><button id="close-attacks-modal" class="settings-btn settings-btn-red">Close</button></div>
                `;
                modal.querySelector('#close-attacks-modal').addEventListener('click', () => { modal.style.display = 'none'; });
            }
        },
        
        showUnauthorizedAttacksModal: async function() {
            // Ensure unauthorized attacks are loaded
            if (!state.unauthorizedAttacks || !state.unauthorizedAttacks.length) {
                await handlers.fetchUnauthorizedAttacks();
            }
            let modal = document.getElementById('unauthorized-attacks-modal');
            if (!modal) {
                modal = document.createElement('div');
                modal.id = 'unauthorized-attacks-modal';
                modal.style.cssText = `
                    position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
                    background-color: #1a1a1a; border: 1px solid #333; border-radius: 8px;
                    padding: 20px; z-index: 10002; box-shadow: 0 4px 8px rgba(0,0,0,0.5);
                    max-width: 800px; width: 90%; color: white; max-height: 80vh; overflow-y: auto;
                `;
                document.body.appendChild(modal);
            }
            let html = `<h3 style="margin-top: 0; text-align: center;">Unauthorized Attacks</h3><span id="unauthorized-attacks-close" style="cursor: pointer; font-size: 18px;">×</span>
                <table style="width:100%;color:white;"><tr><th>Time</th><th>Attacker</th><th>Defender</th><th>Violation</th><th>Result</th></tr>`;
            (state.unauthorizedAttacks || []).forEach(a => {
                html += `<tr>
                    <td style="color: #ffffff;">${new Date(a.attackTime * 1000).toLocaleString()}</td>
                    <td><a href="https://www.torn.com/profiles.php?XID=${a.attackerUserId}" target="_blank" style="color: #ff6b6b; text-decoration: underline; font-weight: bold;">${a.attackerUsername || a.attackerUserId}</a></td>
                    <td><a href="https://www.torn.com/profiles.php?XID=${a.defenderUserId}" target="_blank" style="color: #ffffff; text-decoration: underline; font-weight: bold;">${a.defenderUsername || a.defenderUserId}</a></td>
                    <td style="color: #ffffff;">${a.violationType}</td>
                    <td style="color: #ffffff;">${a.result || ''}</td>
                </tr>`;
            });
            html += `</table>
                <div style="text-align:center;"><button id="close-unauthorized-attacks-btn" style="background:#f44336;color:white;border:none;border-radius:4px;padding:8px 15px;cursor:pointer;font-weight:bold;z-index:10001;">Close</button></div>`;
            modal.innerHTML = html;
            document.getElementById('unauthorized-attacks-close').onclick = () => { modal.style.display = 'none'; };
            document.getElementById('close-unauthorized-attacks-btn').onclick = () => { modal.style.display = 'none'; };
            modal.style.display = 'block';
        },
        showRankedWarSummaryModal: function(summaryData, rankedWarId) {
            // Use the utility function to create the modal if it doesn't exist
            let modal = document.getElementById('ranked-war-summary-modal');
            if (!modal) {
                modal = utils.createElement('div', {
                    id: 'ranked-war-summary-modal',
                    style: {
                        position: 'fixed',
                        top: '50%',
                        left: '50%',
                        transform: 'translate(-50%, -50%)',
                        backgroundColor: config.CSS.colors.modalBg,
                        border: `1px solid ${config.CSS.colors.modalBorder}`,
                        borderRadius: '8px',
                        padding: '20px',
                        zIndex: 10002,
                        boxShadow: '0 4px 8px rgba(0,0,0,0.5)',
                        maxWidth: '90%',
                        width: '1000px',
                        color: 'white',
                        maxHeight: '80vh',
                        overflowY: 'auto'
                    }
                });
                document.body.appendChild(modal);
            }

            // Handle case where no data is available
            if (!summaryData || !Array.isArray(summaryData) || summaryData.length === 0) {
                modal.innerHTML = `
                    <h3 style="margin-top: 0; text-align: center;">Ranked War Summary (ID: ${rankedWarId})</h3>
                    <p style="text-align: center; color: ${config.CSS.colors.error}; margin-bottom: 15px;">
                        No summary data available for this war.
                    </p>
                    <div style="text-align: center;">
                        <button id="close-ranked-war-summary-btn" class="settings-btn settings-btn-red">Close</button>
                    </div>
                `;
                modal.querySelector('#close-ranked-war-summary-btn').addEventListener('click', () => modal.style.display = 'none');
                modal.style.display = 'block';
                return;
            }

            // Group data by faction
            const factionGroups = {};
            summaryData.forEach(attacker => {
                const factionId = attacker.attackerFaction || 'Unknown';
                const factionName = attacker.attackerFactionName || 'Unknown Faction';
                if (!factionGroups[factionId]) {
                    factionGroups[factionId] = {
                        name: factionName,
                        attackers: [],
                        totalAttacks: 0,
                        totalRespect: 0,
                        totalRespectNoChain: 0,
                        totalAssists: 0
                    };
                }
                factionGroups[factionId].attackers.push(attacker);
                factionGroups[factionId].totalAttacks += attacker.totalAttacks || 0;
                factionGroups[factionId].totalRespect += attacker.totalRespectGain || 0;
                factionGroups[factionId].totalRespectNoChain += attacker.totalRespectGainNoChain || 0;
                factionGroups[factionId].totalAssists += attacker.assistCount || 0;
            });

            // Build modal content with template literals
            let contentHTML = `
                <h3 style="margin-top: 0; text-align: center;">Ranked War Summary (ID: ${rankedWarId})</h3>
                <div style="display: flex; justify-content: space-between; margin-bottom: 10px; align-items: center;">
                    <div>
                        <label for="sort-column">Sort by: </label>
                        <select id="sort-column" class="settings-input" style="width: auto;">
                            <option value="attackerName">Name</option>
                            <option value="totalAttacks" selected>Attacks</option>
                            <option value="totalRespectGain">Respect</option>
                            <option value="totalRespectGainNoChain">Respect (No Chain)</option>
                            <option value="averageRespectGain">Avg Respect</option>
                            <option value="averageRespectGainNoChain">Avg Respect (No Chain)</option>
                            <option value="assistCount">Assists</option>
                            <option value="averageModifiers.fair_fight">Fair Fight</option>
                        </select>
                    </div>
                    <div>
                        <label for="sort-direction">Direction: </label>
                        <select id="sort-direction" class="settings-input" style="width: auto;">
                            <option value="desc" selected>Descending</option>
                            <option value="asc">Ascending</option>
                        </select>
                    </div>
                </div>
                <div id="faction-tabs-container" style="display: flex; margin-bottom: 10px; border-bottom: 1px solid #444;"></div>
                <div id="faction-tables-container"></div>
                <div style="text-align: center; margin-top: 15px;">
                    <button id="close-ranked-war-summary-btn" class="settings-btn settings-btn-red">Close</button>
                </div>
            `;
            modal.innerHTML = contentHTML;

            const tabsContainer = modal.querySelector('#faction-tabs-container');
            const tablesContainer = modal.querySelector('#faction-tables-container');

            // Create faction tabs and tables
            Object.keys(factionGroups).forEach((factionId, index) => {
                const faction = factionGroups[factionId];
                const isActive = index === 0;

                // Create Tab Button
                const tabButton = utils.createElement('button', {
                    'data-faction-id': factionId,
                    className: `faction-tab ${isActive ? 'active-tab' : ''}`,
                    textContent: `${faction.name} (${faction.attackers.length})`,
                    style: `flex: 1; padding: 8px; background-color: ${isActive ? config.CSS.colors.success : '#555'}; color: white; border: none; border-top-left-radius: 4px; border-top-right-radius: 4px; cursor: pointer;`
                });
                tabsContainer.appendChild(tabButton);

                // Create Table Container
                const tableContainer = utils.createElement('div', {
                    id: `faction-table-${factionId}`,
                    className: 'faction-table',
                    style: { display: isActive ? 'block' : 'none' },
                    innerHTML: `
                        <div style="margin-bottom: 5px; text-align: right; font-size: 0.9em;">
                            <span style="margin-right: 5px;"><strong>Total Attacks:</strong> ${faction.totalAttacks}</span>
                            <span style="margin-right: 5px;"><strong>Total Respect:</strong> ${faction.totalRespect.toFixed(2)}</span>
                            <span><strong>Total Assists:</strong> ${faction.totalAssists}</span>
                        </div>
                        <table class="summary-table" style="width: 100%; border-collapse: collapse;">
                            <thead>
                                <tr>
                                    <th data-sort="attackerName">Name</th>
                                    <th data-sort="totalAttacks">Attacks</th>
                                    <th data-sort="totalRespectGain">Respect</th>
                                    <th data-sort="totalRespectGainNoChain">RespectNoChain</th>
                                    <th data-sort="averageRespectGain">Avg Respect</th>
                                    <th data-sort="averageRespectGainNoChain">AvgRespectNoChain</th>
                                    <th data-sort="assistCount">Assists</th>
                                    <th data-sort="averageModifiers.fair_fight">FF</th>
                                </tr>
                            </thead>
                            <tbody id="summary-tbody-${factionId}"></tbody>
                        </table>
                    `
                });
                tablesContainer.appendChild(tableContainer);
            });

            // Function to sort data and update table rows
            function sortAndUpdateTables() {
                const sortColumn = modal.querySelector('#sort-column').value;
                const sortDirection = modal.querySelector('#sort-direction').value;

                Object.keys(factionGroups).forEach(factionId => {
                    const faction = factionGroups[factionId];
                    const tbody = modal.querySelector(`#summary-tbody-${factionId}`);
                    if (!tbody) return;

                    // Sort attackers based on selected column and direction
                    const sortedAttackers = [...faction.attackers].sort((a, b) => {
                        let aValue = sortColumn.includes('.') ? a.averageModifiers?.[sortColumn.split('.')[1]] || 0 : a[sortColumn] || 0;
                        let bValue = sortColumn.includes('.') ? b.averageModifiers?.[sortColumn.split('.')[1]] || 0 : b[sortColumn] || 0;

                        if (typeof aValue === 'string') {
                            return sortDirection === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
                        }
                        return sortDirection === 'asc' ? aValue - bValue : bValue - aValue;
                    });

                    // Populate table body
                    tbody.innerHTML = '';
                    sortedAttackers.forEach(attacker => {
                        const row = utils.createElement('tr', {
                            innerHTML: `
                                <td><a href="/profiles.php?XID=${attacker.attackerId}" target="_blank">${attacker.attackerName || `ID ${attacker.attackerId}`}</a></td>
                                <td>${attacker.totalAttacks || 0}</td>
                                <td>${(attacker.totalRespectGain || 0).toFixed(2)}</td>
                                <td>${(attacker.totalRespectGainNoChain || 0).toFixed(2)}</td>
                                <td>${(attacker.averageRespectGain || 0).toFixed(2)}</td>
                                <td>${(attacker.averageRespectGainNoChain || 0).toFixed(2)}</td>
                                <td>${attacker.assistCount || 0}</td>
                                <td>${(attacker.averageModifiers?.fair_fight || 0).toFixed(2)}</td>
                            `
                        });
                        tbody.appendChild(row);
                    });
                });
            }

            // Add Event Listeners
            modal.querySelector('#close-ranked-war-summary-btn').addEventListener('click', () => modal.style.display = 'none');
            modal.querySelector('#sort-column').addEventListener('change', sortAndUpdateTables);
            modal.querySelector('#sort-direction').addEventListener('change', sortAndUpdateTables);

            modal.querySelectorAll('.faction-tab').forEach(tab => {
                tab.addEventListener('click', (e) => {
                    const selectedFactionId = e.currentTarget.dataset.factionId;
                    modal.querySelectorAll('.faction-tab').forEach(t => {
                        t.classList.remove('active-tab');
                        t.style.backgroundColor = '#555';
                    });
                    e.currentTarget.classList.add('active-tab');
                    e.currentTarget.style.backgroundColor = config.CSS.colors.success;

                    modal.querySelectorAll('.faction-table').forEach(table => {
                        table.style.display = table.id === `faction-table-${selectedFactionId}` ? 'block' : 'none';
                    });
                });
            });

            // Add styles for the modal table
            const styleId = 'ranked-summary-modal-styles';
            if (!document.getElementById(styleId)) {
                const style = utils.createElement('style', {
                    id: styleId,
                    textContent: `
                        #ranked-war-summary-modal .summary-table th { padding: 2px; text-align: center; border-bottom: 1px solid #444; cursor: pointer; }
                        #ranked-war-summary-modal .summary-table th:hover { background-color: #333; }
                        #ranked-war-summary-modal .summary-table td { padding: 4px; border-bottom: 1px solid #333; text-align: center; color: #c9c9c9ff }
                        #ranked-war-summary-modal .summary-table td:first-child { text-align: left; }
                        #ranked-war-summary-modal .summary-table a { color: ${config.CSS.colors.success}; text-decoration: none; }
                        #ranked-war-summary-modal .summary-table a:hover { text-decoration: underline; }
                        #ranked-war-summary-modal .active-tab { font-weight: bold; }
                    `
                });
                document.head.appendChild(style);
            }

            // Initial population and display
            sortAndUpdateTables();
            modal.style.display = 'block';
        },

        showAllRetaliationsNotification: function() {
        
            const opportunities = state.retaliationOpportunities;
            const activeOpportunities = Object.values(opportunities || {}).filter(opp => opp.timeRemaining > -60);

            if (activeOpportunities.length === 0) {
                ui.showMessageBox('No active retaliation opportunities available', 'info');
                return;
            }

            // Clean up any previous popups and timers
            let existingPopup = document.getElementById('tdm-retals-popup');
            if (existingPopup) existingPopup.remove();
            state.ui.retalTimerIntervals.forEach(clearInterval);
            state.ui.retalTimerIntervals = [];

            const popupContent = utils.createElement('div', {
                style: {
                    position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', backgroundColor: '#2c2c2c', border: '1px solid #333', borderRadius: '8px',
                    boxShadow: '0 4px 10px rgba(0,0,0,0.5)', padding: '15px', color: 'white', zIndex: 10001, maxWidth: '350px'
                }
            });

            const header = utils.createElement('h3', { textContent: 'Active Retaliation Opportunities', style: { marginTop: '0', marginBottom: '5px', textAlign: 'center' } });
            const list = utils.createElement('ul', { style: { listStyle: 'none', padding: '0', margin: '0', maxHeight: '300px', overflowY: 'auto' } });

            activeOpportunities.forEach(opp => {
                const timeLeftSpan = utils.createElement('span', { style: { color: '#ffcc00' } });
                const alertButton = utils.createElement('button', {
                    textContent: 'Send Alert',
                    style: { backgroundColor: '#ff5722', color: 'white', border: 'none', borderRadius: '4px', padding: '4px 8px', cursor: 'pointer', fontSize: '12px' },
                    onclick: () => ui.sendRetaliationAlert(opp.attackerId, opp.attackerName)
                });
                const listItem = utils.createElement('li', {
                    style: { marginBottom: '15px', padding: '10px', backgroundColor: '#1a1a1a', textAlign: 'center', borderRadius: '5px' }
                }, [
                    utils.createElement('div', {
                        innerHTML: `<a href="https://www.torn.com/loader.php?sid=attack&user2ID=${opp.attackerId}" target="_blank" style="color:#ff6b6b;font-weight:bold;">${opp.attackerName}</a>
                                   <span> attacked ${opp.defenderName} - </span>`
                    }, [ timeLeftSpan ]), // Append the span element here
                    utils.createElement('div', { style: { marginTop: '8px' } }, [alertButton])
                ]);

                // --- COUNTDOWN TIMER LOGIC ---
                const updateCountdown = () => {
                    const timeRemaining = opp.retaliationEndTime - (Date.now() / 1000);
                    if (timeRemaining > 0) {
                        const minutes = Math.floor(timeRemaining / 60);
                        const seconds = Math.floor(timeRemaining % 60);
                        timeLeftSpan.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
                    } else {
                        timeLeftSpan.textContent = `Expired`;
                        timeLeftSpan.style.color = '#aaa';
                        alertButton.disabled = true;
                        alertButton.style.backgroundColor = '#777';
                        // Stop the timer after it expires
                        clearInterval(intervalId);
                        // Remove the item 60 seconds after expiring
                        setTimeout(() => {
                            if (listItem.parentNode) {
                                listItem.parentNode.removeChild(listItem);
                                // If list is empty, close the popup
                                if (list.children.length === 0 && document.getElementById('tdm-retals-popup')) {
                                    document.getElementById('tdm-retals-popup').remove();
                                }
                            }
                        }, 60000);
                    }
                };
                
                updateCountdown(); // Initial call to set the time immediately
                const intervalId = setInterval(updateCountdown, 1000);
                state.ui.retalTimerIntervals.push(intervalId);
                // --- END TIMER LOGIC ---
                
                list.appendChild(listItem);
            });
            
            const dismissButton = utils.createElement('button', {
                textContent: 'Dismiss',
                style: { backgroundColor: '#f44336', color: 'white', border: 'none', borderRadius: '4px', padding: '8px 20px', cursor: 'pointer', display: 'block', margin: '15px auto 0', fontSize: '14px' },
                onclick: (e) => {
                    e.currentTarget.closest('#tdm-retals-popup').remove();
                    // Clear all timers when the user dismisses the popup
                    state.ui.retalTimerIntervals.forEach(clearInterval);
                    state.ui.retalTimerIntervals = [];
                }
            });

            // Only add the list if there are opportunities to show
            if (list.children.length > 0) {
                popupContent.appendChild(header);
                popupContent.appendChild(list);
            } else {
                 popupContent.appendChild(utils.createElement('p', {textContent: 'No active retaliation opportunities.', style: {textAlign: 'center'}}));
            }
            
            popupContent.appendChild(dismissButton);
            const notification = utils.createElement('div', { id: 'tdm-retals-popup' }, [popupContent]);
            document.body.appendChild(notification);
        },
        showTDMAdoptionModal: async function() {
            let modal = document.getElementById('tdm-adoption-modal');
            if (!modal) {
                modal = utils.createElement('div', {
                    id: 'tdm-adoption-modal',
                    style: {
                        position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
                        backgroundColor: config.CSS.colors.modalBg, border: `2px solid ${config.CSS.colors.modalBorder}`,
                        borderRadius: '4px', padding: '4px', zIndex: 10000, color: 'white',
                        width: '90%', maxWidth: '1000px', minWidth: '320px',
                        maxHeight: '80vh', overflowY: 'auto', overflowX: 'auto', boxShadow: '0 4px 8px rgba(0,0,0,0.5)'
                    }
                });
                document.body.appendChild(modal);
            }
            modal.innerHTML = `
                <button class='tdm-adoption-close' style='position:absolute;top:8px;right:12px;background:${config.CSS.colors.error};color:white;border:none;border-radius:4px;padding:4px 10px;cursor:pointer;font-weight:bold;z-index:10001;'>X</button>
                <h2 style='margin-top:0;'>Faction TDM Adoption</h2>
                <div id='tdm-adoption-loading'>Loading adoption stats...</div>
            `;
            modal.style.display = 'block';

            // Bind close button early so it works even if we return on error
            if (!modal._tdmCloseBound) {
                modal.addEventListener('click', (event) => {
                    if (event.target.classList.contains('tdm-adoption-close')) {
                        modal.style.display = 'none';
                    }
                });
                modal._tdmCloseBound = true;
            }

            // Helper: safe Firestore Timestamp/string/number -> Date or null
            const toSafeDate = (val) => {
                try {
                    if (!val) return null;
                    if (val instanceof Date) return isNaN(val.getTime()) ? null : val;
                    if (typeof val?.toMillis === 'function') return new Date(val.toMillis());
                    if (typeof val?._seconds === 'number') return new Date(val._seconds * 1000);
                    if (typeof val?.seconds === 'number') return new Date(val.seconds * 1000);
                    if (typeof val === 'number') return new Date(val);
                    if (typeof val === 'string') {
                        const d = new Date(val);
                        return isNaN(d.getTime()) ? null : d;
                    }
                } catch (_) { /* ignore */ }
                return null;
            };

            let tdmUsers = [];
            try {
                const apiUsers = await api.get('getTDMUsersByFaction', { factionId: state.user.factionId });
                // Defensive: ensure array
                tdmUsers = Array.isArray(apiUsers) ? apiUsers : [];

                // Convert lastVerified safely (avoid reading undefined._seconds)
                tdmUsers = tdmUsers.map(u => ({
                    ...u,
                    lastVerified: toSafeDate(u.lastVerified)
                }));
                // remove users whos position is 'Resting in Elysian Fields'
                tdmUsers = tdmUsers.filter(u => u.position !== 'Resting in Elysian Fields');
            } catch (e) {
                const loadingDiv = modal.querySelector('#tdm-adoption-loading');
                if (loadingDiv) loadingDiv.remove();
                console.error('Error fetching TDM users:', e);
                modal.innerHTML += `<p style='color:${config.CSS.colors.error};'>Error fetching TDM user data.</p>`;
                // Close button already bound above
                return;
            }

            const members = Array.isArray(state.factionMembers) ? state.factionMembers : [];
            const merged = members.map(m => {
                // pick most recent record for this member (if any)
                const recs = tdmUsers.filter(u => String(u.tornId) === String(m.id));
                let mostRecent = null;
                if (recs.length > 0) {
                    mostRecent = recs.reduce((latest, current) => {
                        const a = toSafeDate(latest?.lastVerified);
                        const b = toSafeDate(current?.lastVerified);
                        return (b && (!a || b > a)) ? current : latest;
                    });
                }
                const lastVerified = toSafeDate(mostRecent?.lastVerified);
                return {
                    id: m.id,
                    name: m.name,
                    level: m.level,
                    days: m.days_in_faction,
                    position: m.position,
                    isTDM: !!mostRecent,
                    tdmVersion: mostRecent?.version || '',
                    last_action: new Date(Number(m.last_action?.timestamp || 0) * 1000) || '',
                    lastVerified: lastVerified && lastVerified.getTime() > 0 ? lastVerified : ''
                };
            });

            const adoptedCount = merged.filter(m => m.isTDM).length;
            const totalCount = merged.length;
            const percent = totalCount ? Math.round((adoptedCount / totalCount) * 100) : 0;

            const loadingDiv = modal.querySelector('#tdm-adoption-loading');
            if (loadingDiv) loadingDiv.remove();

            modal.innerHTML += `
                <div style='margin-bottom:16px;'>
                    <div style='font-size:1.1em;'>${adoptedCount} of ${totalCount} members have installed TDM (${percent}%)</div>
                    <div style='background:#333; border-radius:6px; height:22px; width:100%; margin-top:8px; position:relative;'>
                        <div style='background:${config.CSS.colors.success}; height:100%; border-radius:6px; width:${percent}%; transition:width 0.5s;'></div>
                        <div style='position:absolute; left:50%; top:0; transform:translateX(-50%); color:white; font-weight:bold; line-height:22px;'>${percent}%</div>
                    </div>
                </div>
                <div style='margin-bottom:8px;'><b>Sortable Member Table</b></div>
                <table id='tdm-adoption-table' style='width:100%; min-width:900px; border-collapse:collapse; background:#222; color:white;'>
                    <thead>
                        <tr style='background:#333;'>
                            <th style='padding:6px; cursor:pointer;' data-sort='name'>Name</th>
                            <th style='padding:6px; cursor:pointer;' data-sort='level'>Lvl</th>
                            <th style='padding:6px; cursor:pointer;' data-sort='position'>Position</th>
                            <th style='padding:6px; cursor:pointer;' data-sort='days'>Days</th>
                            <th style='padding:6px; cursor:pointer;' data-sort='isTDM'>TDM?</th>
                            <th style='padding:6px; cursor:pointer;' data-sort='tdmVersion'>Version</th>
                            <th style='padding:6px; cursor:pointer;' data-sort='last_action'>Last Action</th>
                            <th style='padding:6px; cursor:pointer;' data-sort='lastVerified'>Last Verified</th>
                        </tr>
                    </thead>
                    <tbody>
                        ${merged.map(m => `
                            <tr style='background:#2c2c2c; color:white;'>
                                <td style='padding:6px; color:white;'>${m.name || ''}</td>
                                <td style='padding:6px; color:white; text-align:center;'>${m.level || ''}</td>
                                <td style='padding:6px; color:white;'>${m.position || ''}</td>
                                <td style='padding:6px; color:white; text-align:center;'>${m.days || ''}</td>
                                <td style='padding:6px; color:white; text-align:center;'>${m.isTDM ? '✅' : ''}</td>
                                <td style='padding:6px; color:white;'>${m.tdmVersion || ''}</td>
                                <td style='padding:6px; color:white;'>${m.last_action ? m.last_action.toLocaleDateString(undefined, { year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : ''}</td>
                                <td style='padding:6px; color:white;'>${m.lastVerified ? m.lastVerified.toLocaleDateString(undefined, { year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : ''}</td>
                            </tr>
                        `).join('')}
                    </tbody>
                </table>
                <div style='margin-top:12px; font-size:0.95em; color:#aaa;'>TDM = TreeDibsMapper userscript installed and verified with backend.</div>
            `;

            if (!modal.querySelector('#tdm-adoption-dismiss')) {
                const dismissBtn = utils.createElement('button', {
                    id: 'tdm-adoption-dismiss',
                    className: 'settings-btn settings-btn-red',
                    style: { marginTop: '8px', display: 'block', width: '100%' },
                    textContent: 'Dismiss',
                    onclick: () => { modal.style.display = 'none'; }
                });
                modal.appendChild(dismissBtn);
            }

            const table = modal.querySelector('#tdm-adoption-table');
            if (table) {
                let sortKey = 'lastVerified', sortAsc = false;
                const asSortable = (val) => {
                    if (val instanceof Date) return val.getTime();
                    if (typeof val === 'number') return val;
                    if (typeof val === 'boolean') return val ? 1 : 0;
                    return (val || '').toString().toLowerCase();
                };
                const renderRows = () => {
                    const sorted = [...merged].sort((a, b) => {
                        const A = asSortable(a[sortKey]);
                        const B = asSortable(b[sortKey]);
                        if (A === B) return 0;
                        return sortAsc ? (A > B ? 1 : -1) : (A < B ? 1 : -1);
                    });
                    table.querySelector('tbody').innerHTML = sorted.map(m => `
                        <tr style='background:#2c2c2c; color:white;'>
                            <td style='padding:6px; color:white;'>${m.name || ''}</td>
                            <td style='padding:6px; color:white; text-align:center;'>${m.level || ''}</td>
                            <td style='padding:6px; color:white;'>${m.position || ''}</td>
                            <td style='padding:6px; color:white; text-align:center;'>${m.days || ''}</td>
                            <td style='padding:6px; color:white; text-align:center;'>${m.isTDM ? '✅' : ''}</td>
                            <td style='padding:6px; color:white;'>${m.tdmVersion || ''}</td>
                            <td style='padding:6px; color:white;'>${m.last_action ? m.last_action.toLocaleDateString(undefined, { year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : ''}</td>
                            <td style='padding:6px; color:white;'>${m.lastVerified ? m.lastVerified.toLocaleDateString(undefined, { year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : ''}</td>
                        </tr>
                    `).join('');
                };
                table.querySelectorAll('th').forEach(th => {
                    th.onclick = () => {
                        const key = th.getAttribute('data-sort');
                        if (sortKey === key) sortAsc = !sortAsc;
                        else { sortKey = key; sortAsc = true; }
                        renderRows();
                    };
                });
                renderRows();
            }
        }
    };

    //======================================================================
    // 6. EVENT HANDLERS & CORE LOGIC
    //======================================================================
    const handlers = {
        fetchGlobalData: async () => {
            utils.perf.start('fetchGlobalData');
            // Re-entrancy/throttle guard (PDA can trigger rapid duplicate calls)
            const nowMsFG = Date.now();
            if (state.script._lastGlobalFetch && (nowMsFG - state.script._lastGlobalFetch) < config.MIN_GLOBAL_FETCH_INTERVAL_MS) {
                utils.perf.stop('fetchGlobalData');
                return;
            }
            state.script._lastGlobalFetch = nowMsFG;
            if (!state.user.tornId || !state.user.actualTornApiKey) {
                console.warn("[TDM] fetchGlobalData: User/API key missing.");
                utils.perf.stop('fetchGlobalData');
                return;
            }

            try {
                // Call new backend endpoint
                const clientTimestamps = state.dataTimestamps;
                const globalData = await api.post('getGlobalDataForUser', {
                    tornId: state.user.tornId,
                    tornApiKey: state.user.actualTornApiKey,
                    factionId: state.user.factionId,
                    clientTimestamps,
                    lastActivityTime: state.script.lastActivityTime,
                    visibleOpponentIds: utils.getVisibleOpponentIds(),
                    clientNoteTimestamps: utils.getClientNoteTimestamps()
                });
                
                const TRACKED_COLLECTIONS = [
                    'dibs',
                    'userNotes',
                    'medDeals',
                    'rankedWars',
                    'rankedWars_attacks',
                    'rankedWars_summary',
                    'unauthorizedAttacks',
                    'attackerActivity',
                    'TornAPICalls_rankedwars',
                    'warData'
                ];
                // Check if collections have changed
                if (utils.isCollectionChanged(clientTimestamps, globalData.masterTimestamps, 'TornAPICalls_rankedwars')) {
                    storage.updateStateAndStorage('rankWars', globalData.tornApi.rankWars);
                    storage.updateStateAndStorage('lastRankWar', state.rankWars.length > 0 ? state.rankWars[0] : state.lastRankWar);
                    if (state.lastRankWar && state.lastRankWar.factions) {
                        const opponentFaction = Object.values(state.lastRankWar.factions).find(f => f.id !== parseInt(state.user.factionId));
                        if (opponentFaction) {
                            if (state.warData.opponentFactionId && state.warData.opponentFactionId != opponentFaction.id) {

                                storage.updateStateAndStorage('warData', { opponentFactionId: opponentFaction.id, opponentFactionName: opponentFaction.name });
                            }
                            storage.updateStateAndStorage('lastOpponentFactionId', opponentFaction.id);
                            storage.updateStateAndStorage('lastOpponentFactionName', opponentFaction.name);
                        }
                    }
                    state.dataTimestamps.TornAPICalls_rankedwars = globalData.masterTimestamps.TornAPICalls_rankedwars;
                    storage.set('dataTimestamps', state.dataTimestamps);
                    
                }
                if (utils.isCollectionChanged(clientTimestamps, globalData.masterTimestamps, 'rankedWars')) {
                    console.log(`[TDM][rankedwars][Master]`, globalData.tornApi.warData)
                    console.log(`[TDM][rankedwars][Client]`, state.warData)

                    storage.updateStateAndStorage('warData', globalData.tornApi.warData);
                    state.dataTimestamps.rankedWars = globalData.masterTimestamps.rankedWars;
                    storage.set('dataTimestamps', state.dataTimestamps); 
                }

                if (utils.isCollectionChanged(clientTimestamps, globalData.masterTimestamps, 'dibs')) {
                    console.log(`[TDM][dibs][Master]`, globalData.firebase.dibsData)
                    console.log(`[TDM][dibs][Client]`, state.dibsData)
                    // Dibs data
                    storage.updateStateAndStorage('dibsData', globalData.firebase.dibsData);
                    state.dataTimestamps.dibs = globalData.masterTimestamps.dibs;
                    storage.set('dataTimestamps', state.dataTimestamps);
                }
                if (utils.isCollectionChanged(clientTimestamps, globalData.masterTimestamps, 'medDeals')) {
                    console.log(`[TDM][medDeals][Master]`, globalData.firebase.opponentStatuses)
                    console.log(`[TDM][medDeals][Client]`, state.opponentStatuses)
                    // Opponent statuses aka medical deals
                    var medDeals = {};
                    if (Array.isArray(globalData.firebase.opponentStatuses)) {
                        globalData.firebase.opponentStatuses.forEach(status => { medDeals[status.id] = status; });
                    }
                    storage.updateStateAndStorage('opponentStatuses', medDeals);
                    state.dataTimestamps.medDeals = globalData.masterTimestamps.medDeals;
                    storage.set('dataTimestamps', state.dataTimestamps);
                }
                if (utils.isCollectionChanged(clientTimestamps, globalData.masterTimestamps, 'userNotes')) {
                    // Merge delta results
                    const delta = Array.isArray(globalData.firebase.userNotesDelta) ? globalData.firebase.userNotesDelta : [];
                    const missing = Array.isArray(globalData.firebase.userNotesMissing) ? globalData.firebase.userNotesMissing : [];
                    if (delta.length > 0 || missing.length > 0) {
                        const merged = { ...(state.userNotes || {}) };
                        for (const note of delta) {
                            if (!note || !note.id) continue;
                            merged[note.id] = note;
                        }
                        for (const id of missing) {
                            if (id in merged) delete merged[id];
                        }
                        storage.updateStateAndStorage('userNotes', merged);
                        state.dataTimestamps.userNotes = globalData.masterTimestamps.userNotes;
                        storage.set('dataTimestamps', state.dataTimestamps);
                    }
                }
                
                if (utils.isCollectionChanged(clientTimestamps, globalData.masterTimestamps, 'dibsNotifications')) {
                    console.log(`[TDM][dibsNotifications][Master]`, globalData.firebase.dibsNotifications)
                    console.log(`[TDM][dibsNotifications][Client]`, state.dataTimestamps.dibsNotifications)
                    // Dibs notifications
                    if (Array.isArray(globalData.firebase.dibsNotifications) && globalData.firebase.dibsNotifications.length > 0) {
                        const notifications = globalData.firebase.dibsNotifications;
                        notifications.sort((a, b) => (b.createdAt?._seconds || 0) - (a.createdAt?._seconds || 0));
                        notifications.forEach(notification => {
                            const message = `Your dibs on ${notification.opponentName} was removed. Reason: ${notification.removalReason}. Click to dismiss.`;
                            let isMarked = false;
                            const markAsRead = async () => {
                                if (isMarked) return;
                                isMarked = true;
                                await api.post('markDibsNotificationAsRead', { notificationId: notification.id });
                            };
                            ui.showMessageBox(message, 'warning', 60000, markAsRead);
                            setTimeout(markAsRead, 60000);
                        });
                    }
                    state.dataTimestamps.dibsNotifications = globalData.masterTimestamps.dibsNotifications;
                    storage.set('dataTimestamps', state.dataTimestamps);
                }
                if (utils.isCollectionChanged(clientTimestamps, globalData.masterTimestamps, 'rankedWars_summary')) {
                    // console.log(`[TDM][rankedWars_summary][Master]`, globalData.firebase.rankedWarSummary)
                    // console.log(`[TDM][rankedWars_summary][Client]`, state.rankedWarSummary)
                    // Ranked war summary (for score cap check)
                    storage.updateStateAndStorage('rankedWarSummary', globalData.firebase.rankedWarSummary);
                    state.dataTimestamps.rankedWars_summary = globalData.masterTimestamps.rankedWars_summary;
                    storage.set('dataTimestamps', state.dataTimestamps);
                }

                // Score cap check
                await handlers.checkTermedWarScoreCap();

                ui.updateAllPages();
                // Run enforcement pass (auto-removals) after UI updates
                handlers.enforceDibsPolicies?.();

            } catch (error) {
                console.error("[TDM] Error fetching global data:", error);
            }
            utils.perf.stop('fetchGlobalData');
        },
        fetchUnauthorizedAttacks: async () => {
            try {
                const response = await api.get('getUnauthorizedAttacks', { factionId: state.user.factionId });
                // Persist to state/storage so UI modals render results immediately
                const list = Array.isArray(response) ? response : [];
                storage.updateStateAndStorage('unauthorizedAttacks', list);
                return list;
            } catch (error) {
                if (error?.message?.includes('FAILED_PRECONDITION')) {
                    storage.updateStateAndStorage('unauthorizedAttacks', []);
                    return [];
                }
                console.warn('[TDM] Non-critical error fetching unauthorized attacks:', error.message || 'Unknown error');
                storage.updateStateAndStorage('unauthorizedAttacks', []);
                return [];
            }
        },
        fetchRetaliationOpportunities: async () => {
            // Compute locally using user's limited key
            try {
                const nowMsRet = Date.now();
                if (state.script._lastRetalsFetch && (nowMsRet - state.script._lastRetalsFetch) < config.MIN_RETALS_FETCH_INTERVAL_MS) {
                    return;
                }
                state.script._lastRetalsFetch = nowMsRet;
                const apiKey = state?.user?.actualTornApiKey;
                if (!apiKey) return;
                const now = Math.floor(Date.now() / 1000);
                const sixMinutesAgo = now - 360; // buffer window
                const url = `https://api.torn.com/v2/faction/attacks?sort=DESC&from=${sixMinutesAgo}&key=${apiKey}&comment=TreeDibsMapperRetals`;
                let data = null;
                try {
                    const res = await fetch(url);
                    data = await res.json();
                    utils.incrementApiCalls(1);
                } catch (e) {
                    // Fallback to GM request if fetch blocked (PDA)
                    data = await new Promise((resolve) => {
                        try {
                            state.gm?.rD_xmlhttpRequest?.({
                                method: 'GET', url,
                                onload: r => { try { const parsed = JSON.parse(r.responseText); utils.incrementApiCalls(1); resolve(parsed); } catch { resolve({}); } },
                                onerror: () => resolve({})
                            });
                        } catch { resolve({}); }
                    });
                }
                if (data && data.error) return;
                const attacks = Object.values(data?.attacks || {});
            const incoming = {}; // keyed by attackerId
            const outgoing = {}; // keyed by defenderId (attacker we hit)
            const our = state.user.factionId?.toString();
                for (const atk of attacks) {
            const defFac = atk?.defender?.faction?.id?.toString();
            const atkFac = atk?.attacker?.faction?.id?.toString();
                    const validRes = atk?.result !== 'Lost' && atk?.result !== 'Stalemate';
                    if (!validRes) continue;
            // Incoming: they attacked our member
            if (defFac === our && atkFac !== our) {
                        const prev = incoming[atk.attacker.id];
                        if (!prev || atk.ended > prev.ended) incoming[atk.attacker.id] = atk;
                    }
            // Outgoing: our member attacked them
            if (atkFac === our) {
                        const prev = outgoing[atk.defender.id];
                        if (!prev || atk.ended > prev.ended) outgoing[atk.defender.id] = atk;
                    }
                }
                const newRetaliationOpportunities = {};
                for (const [attackerId, atk] of Object.entries(incoming)) {
                    const fulfilled = !!outgoing[attackerId] && outgoing[attackerId].ended > atk.ended;
                    if (!fulfilled) {
                        const timeRemaining = (atk.ended + 300) - now; // 5 minutes
                        if (timeRemaining > -60) {
                            newRetaliationOpportunities[attackerId] = {
                                attackerId: Number(attackerId),
                                attackerName: atk.attacker?.name,
                                defenderId: atk.defender?.id,
                                defenderName: atk.defender?.name,
                                retaliationEndTime: atk.ended + 300,
                                timeRemaining: timeRemaining > 0 ? timeRemaining : 0,
                                expired: timeRemaining <= 0
                            };
                        }
                    }
                }
                storage.updateStateAndStorage('retaliationOpportunities', newRetaliationOpportunities);
                ui.updateRetalsButtonCount?.();
            } catch (error) {
                console.error('[TDM] Local retals error:', error);
            }
        },
        checkAndDisplayDibsNotifications: async () => {
            if (!state.user.tornId) return;
            try {
                let notifications = await api.get('getDibsNotifications', { factionId: state.user.factionId });
                if (Array.isArray(notifications) && notifications.length > 0) {
                    notifications.sort((a, b) => (b.createdAt?._seconds || 0) - (a.createdAt?._seconds || 0));
                    notifications.forEach(notification => {
                        const message = `Your dibs on ${notification.opponentName} was removed. Reason: ${notification.removalReason}. Click to dismiss.`;
                        let isMarked = false;
                        const markAsRead = async () => {
                            if (isMarked) return;
                            isMarked = true;
                            await api.post('markDibsNotificationAsRead', { notificationId: notification.id, factionId: state.user.factionId });
                        };
                        ui.showMessageBox(message, 'warning', 60000, markAsRead);
                        setTimeout(markAsRead, 60000);
                    });
                }
            } catch (error) {
                console.error('[TDM] Error fetching dibs notifications:', error);
            }
        },
        dibsTarget: async (opponentId, opponentName, buttonElement) => {
            const originalText = buttonElement.textContent;
            buttonElement.disabled = true;
            buttonElement.innerHTML = '<span class="dibs-spinner"></span>';
            try {
                const opts = utils.getDibsStyleOptions();
                const [oppStat, meStat] = await Promise.all([
                    utils.getUserStatus(opponentId),
                    utils.getUserStatus(null)
                ]);
                const myCanon = meStat.canonical;
                if (opts.allowedUserStatuses && opts.allowedUserStatuses[myCanon] === false) {
                    throw new Error(`Your status (${myCanon}) is not allowed to place dibs by faction policy.`);
                }
                const canonOpp = oppStat.canonical;
                if (opts.allowStatuses && opts.allowStatuses[canonOpp] === false) {
                    throw new Error(`Target status (${canonOpp}) is not allowed by faction policy.`);
                }
                // If configured: limit dibbing a Hospital opponent to those with release time under N minutes
                if (canonOpp === 'Hospital') {
                    const limitMin = Number(opts.maxHospitalReleaseMinutes || 0);
                    if (limitMin > 0) {
                        const remaining = Math.max(0, (oppStat.until || 0) - Math.floor(Date.now() / 1000));
                        if (remaining > limitMin * 60) {
                            throw new Error(`Target is hospitalized too long (${Math.ceil(remaining/60)}m). Policy allows < ${limitMin}m.`);
                        }
                    }
                }
                await api.post('dibsTarget', { userid: state.user.tornId, username: state.user.tornUsername, opponentId, opponentname: opponentName, warType: state.warData.warType, factionId: state.user.factionId });
                ui.showMessageBox(`Successfully dibbed ${opponentName}!`, 'success');
                await handlers.fetchGlobalData();
            } catch (error) {
                // Friendly handling if target is already dibbed
                const msg = String(error?.message || 'Unknown error');
                const already = msg.toLowerCase().includes('already') || error?.code === 'already-exists' || error?.alreadyDibbed === true;
                if (already) {
                    const dibberName = error?.dibber?.name || error?.dibberName || 'Someone';
                    ui.showMessageBox(`${opponentName} is already dibbed by ${dibberName}.`, 'info');
                    // Update button to reflect current dib state immediately
                    buttonElement.className = 'btn dibs-btn btn-dibs-success-other';
                    buttonElement.textContent = dibberName;
                    // Admins can remove; regular users cannot
                    const canRemove = state.script.canAdministerMedDeals && (storage.get('adminFunctionality', true) === true);
                    buttonElement.disabled = !canRemove;
                    if (canRemove) {
                        buttonElement.onclick = (e) => handlers.debouncedRemoveDibsForTarget(opponentId, e.currentTarget);
                    }
                    // Also kick off a background refresh to hydrate full state
                    handlers.debouncedFetchGlobalData();
                } else {
                    ui.showMessageBox(`Failed to dib target: ${msg}`, 'error');
                    buttonElement.disabled = false;
                    buttonElement.textContent = originalText;
                }
            }
        },
        removeDibsForTarget: async (opponentId, buttonElement) => {
            const originalText = buttonElement.textContent;
            buttonElement.disabled = true;
            buttonElement.innerHTML = '<span class="dibs-spinner"></span>';
            try {
                const dib = state.dibsData.find(d => d.opponentId === opponentId && d.dibsActive);
                if (dib) {
                    const confirmed = await ui.showConfirmationBox(`Remove ${dib.username}'s dibs for ${dib.opponentname}?`);
                    if (confirmed) {
                        await api.post('removeDibs', { dibsDocId: dib.id, removedByUsername: state.user.tornUsername, factionId: state.user.factionId });
                        ui.showMessageBox('Dibs removed!', 'success');
                        await handlers.fetchGlobalData();
                    } else {
                        buttonElement.disabled = false;
                        buttonElement.textContent = originalText;
                    }
                } else {
                    ui.showMessageBox('No active dibs to remove.', 'info');
                    buttonElement.disabled = false;
                    buttonElement.textContent = originalText;
                }
            } catch (error) {
                ui.showMessageBox(`Failed to remove dibs: ${error.message}`, 'error');
                buttonElement.disabled = false;
                buttonElement.textContent = originalText;
            }
        },
        handleMedDealToggle: async (opponentId, opponentName, setMedDeal, medDealForUserId, medDealForUsername, buttonElement) => {
            const originalText = buttonElement.innerHTML;
            buttonElement.disabled = true;
            buttonElement.innerHTML = '<span class="dibs-spinner"></span>';
            try {
                if (!setMedDeal) {
                    const confirmed = await ui.showConfirmationBox(`Remove Med Deal with ${opponentName}?`);
                    if (!confirmed) {
                        buttonElement.disabled = false;
                        buttonElement.innerHTML = originalText;
                        return;
                    }
                }
                await api.post('updateMedDeal', {
                    actionInitiatorUserId: state.user.tornId,
                    actionInitiatorUsername: state.user.tornUsername,
                    targetOpponentId: opponentId,
                    opponentName,
                    setMedDeal,
                    medDealForUserId,
                    medDealForUsername,
                    factionId: state.user.factionId
                });
                ui.showMessageBox(`Med Deal with ${opponentName} ${setMedDeal ? 'set' : 'removed'}.`, 'success');
                await handlers.fetchGlobalData();
            } catch (error) {
                ui.showMessageBox(`Failed to update Med Deal: ${error.message}`, 'error');
                buttonElement.disabled = false;
                buttonElement.innerHTML = originalText;
            }
        },
        checkTermedWarScoreCap: async () => {
            // Only run this check if it's a termed war with a score cap set
            if (state.warData.warType !== 'Termed War' || !state.warData.scoreCap || state.warData.scoreCap <= 0) {
                return;
            }
            // --- NEW LOGIC START ---
            const warId = state.lastRankWar?.id;
            if (!warId) return; // Exit if we don't have a valid war ID
            
                        
            const storageKey = `scoreCapAcknowledged_${warId}`;

            // Check if the user has already acknowledged the cap for this specific war
            if (storage.get(storageKey, false)) {
                // console.log(`acknowledge already: ${storage.get(storageKey, false)}`);
                state.user.hasReachedScoreCap = true; // Set session state for attack page warnings
                return; // Exit to prevent showing the popup again
            }
            // Stop if the user has already been notified in this session (fallback check)
            if (state.user.hasReachedScoreCap) {
                // console.log('backupcheck...');
                return;
            }
            // utils.perf.start('checkTermedWarScoreCap');
            try {
                // console.log('Getting Score');
                // Get the latest war summary, which includes the user's score
                const summary = await api.get('rankedWarSummary', { rankedWarId: warId, factionId: state.user.factionId });
                if (!summary || summary.length === 0) return;

                const userSummary = summary.find(s => s.attackerId == state.user.tornId);
                // console.log(`userSummary`, userSummary);
                if (!userSummary) return;

                const scoreType = state.warData.scoreType || 'Respect'; // Default to Respect if not set
                let userScore = 0;

                if (scoreType === 'Respect') {
                    userScore = userSummary.totalRespectGain || 0;
                } else if (scoreType === 'Respect (no chain)') {
                    userScore = userSummary.totalRespectGainNoChain || 0;
                } else { // Attacks
                    userScore = userSummary.totalAttacks || 0;
                }
                console.log(`Current Score: ${userScore} ${scoreType}`);
                // Check if the user's score has met or exceeded the cap
                if (userScore >= state.warData.scoreCap) {
                    state.user.hasReachedScoreCap = true; // Set session flag immediately

                    const confirmed = await ui.showConfirmationBox(
                        'You have reached your target score. Do not make any more attacks. Your dibs and med deals will be deactivated.',
                        false // This shows an "OK" button instead of "Yes/No"
                    );

                    if (confirmed) {
                        storage.set(storageKey, true);

                        // Call the backend to deactivate everything for the user
                        await api.post('deactivateDibsAndDealsForUser', { userId: state.user.tornId, factionId: state.user.factionId });
                        // Refresh data to show the deactivated status
                        await handlers.fetchGlobalData();
                    }
                }
            } catch (error) {
                console.error("[TDM] Error checking score cap:", error);
                // Don't show an error to the user, just log it.
            }
            // utils.perf.stop('checkTermedWarScoreCap');
            
        },
        setFactionWarData: async (warData, buttonElement) => {
            const originalText = buttonElement.textContent;
            buttonElement.disabled = true;
            buttonElement.innerHTML = '<span class="dibs-spinner"></span> Saving...';
            try {
                await api.post('setWarData', { warId: state.lastRankWar.id.toString(), warData, factionId: state.user.factionId });
                ui.showMessageBox('War data saved!', 'success');
                await handlers.fetchGlobalData();
            } catch (error) {
                ui.showMessageBox(`Failed to save war data: ${error.message}`, 'error');
            } finally {
                buttonElement.disabled = false;
                buttonElement.textContent = originalText;
            }
        },
        handleSaveUserNote: async (noteTargetId, noteContent, buttonElement) => {
            const originalText = buttonElement.textContent;
            buttonElement.disabled = true;
            buttonElement.innerHTML = '<span class="dibs-spinner"></span>';
            try {
                await api.post('updateUserNote', { noteTargetId, noteContent, factionId: state.user.factionId });
                ui.showMessageBox('[TDM] Note saved!', 'success');
                state.userNotes[noteTargetId] = { noteContent };
                ui.closeNoteModal();
                await handlers.fetchGlobalData();
            } catch (error) {
                ui.showMessageBox(`[TDM] Failed to save note: ${error.message}`, 'error');
                buttonElement.disabled = false;
                buttonElement.textContent = originalText;
            }
        },
        assignDibs: async (opponentId, opponentName, dibsForUserId, dibsForUsername, buttonElement) => {
            const originalText = buttonElement.textContent;
            buttonElement.disabled = true;
            buttonElement.innerHTML = '<span class="dibs-spinner"></span>';
            try {
                const opts = utils.getDibsStyleOptions();
                const [oppStat, assigneeStat] = await Promise.all([
                    utils.getUserStatus(opponentId),
                    utils.getUserStatus(dibsForUserId)
                ]);
                const assCanon = assigneeStat.canonical;
                if (opts.allowedUserStatuses && opts.allowedUserStatuses[assCanon] === false) {
                    throw new Error(`User status (${assCanon}) is not allowed to place dibs by faction policy.`);
                }
                const canonOpp = oppStat.canonical;
                if (opts.allowStatuses && opts.allowStatuses[canonOpp] === false) {
                    throw new Error(`Target status (${canonOpp}) is not allowed by faction policy.`);
                }
                if (canonOpp === 'Hospital') {
                    const limitMin = Number(opts.maxHospitalReleaseMinutes || 0);
                    if (limitMin > 0) {
                        const remaining = Math.max(0, (oppStat.until || 0) - Math.floor(Date.now() / 1000));
                        if (remaining > limitMin * 60) {
                            throw new Error(`Target is hospitalized too long (${Math.ceil(remaining/60)}m). Policy allows < ${limitMin}m.`);
                        }
                    }
                }
                await api.post('dibsTarget', {
                    userid: dibsForUserId,
                    username: dibsForUsername,
                    opponentId,
                    opponentname: opponentName,
                    warType: state.warData.warType,
                    factionId: state.user.factionId
                });
                ui.showMessageBox(`[TDM] Assigned dibs on ${opponentName} to ${dibsForUsername}!`, 'success');
                await handlers.fetchGlobalData();
            } catch (error) {
                const msg = String(error?.message || 'Unknown error');
                const already = msg.toLowerCase().includes('already') || error?.code === 'already-exists' || error?.alreadyDibbed === true;
                if (already) {
                    const dibberName = error?.dibber?.name || error?.dibberName || 'Someone';
                    ui.showMessageBox(`${opponentName} is already dibbed by ${dibberName}.`, 'info');
                    // Setter button may not be the row dibs button; do a light refresh so row updates
                    handlers.debouncedFetchGlobalData();
                } else {
                    ui.showMessageBox(`[TDM] Failed to assign dibs: ${msg}`, 'error');
                }
                buttonElement.disabled = false;
                buttonElement.textContent = originalText;
            }
        },
        // Auto-enforcement sweep: remove dibs when opponent/user travels if policy enabled
        enforceDibsPolicies: async () => {
            try {
                const now = Date.now();
                if (now - (state.session.lastEnforcementMs || 0) < 8000) return; // every ~8s max
                state.session.lastEnforcementMs = now;
                const opts = utils.getDibsStyleOptions();
                const myActive = state.dibsData.find(d => d.dibsActive && d.userId === state.user.tornId);
                if (!myActive) return;
                // Check opponent travel removal
                if (opts.removeOnFly) {
                    try {
                        const oppStat = await utils.getUserStatus(myActive.opponentId);
                        if (oppStat.canonical === 'Travel' || oppStat.canonical === 'Abroad') {
                            await api.post('removeDibs', { dibsDocId: myActive.id, removedByUsername: state.user.tornUsername, factionId: state.user.factionId });
                            ui.showMessageBox(`Your dib on ${myActive.opponentname || myActive.opponentId} was removed (opponent traveling policy).`, 'warning');
                            handlers.debouncedFetchGlobalData();
                            return;
                        }
                    } catch (_) { /* ignore */ }
                }
                // Check user travel removal
                if (opts.removeWhenUserTravels) {
                    try {
                        const me = await utils.getUserStatus(null);
                        if (me.canonical === 'Travel' || me.canonical === 'Abroad') {
                            await api.post('removeDibs', { dibsDocId: myActive.id, removedByUsername: state.user.tornUsername, factionId: state.user.factionId });
                            ui.showMessageBox(`Your dib on ${myActive.opponentname || myActive.opponentId} was removed (your travel policy).`, 'warning');
                            handlers.debouncedFetchGlobalData();
                            return;
                        }
                    } catch (_) { /* ignore */ }
                }
            } catch (e) { /* non-fatal */ }
        },
        // Check OC status once per userscript load
        checkOCReminder: async () => {
            const ocReminderEnabled = storage.get('ocReminderEnabled', true);
            const ocReminderShown = storage.get('ocReminderShown', false);
 
            if (ocReminderEnabled && !ocReminderShown) {
                const currentUser = state.factionMembers.find(member => member.id == state.user.tornId);
                // console.log("[TDM] Current user in OC check:", currentUser.is_in_oc);
                if (currentUser && !currentUser.is_in_oc) {
                    ui.showConfirmationBox('[TDM] JOIN AN OC!');
                    storage.set('ocReminderShown', true); // Ensure it only shows once per load
                }
            }
        }
        
    };

    //======================================================================
    // 7. INITIALIZATION & MAIN EXECUTION
    //======================================================================
    const main = {
        init: async () => {
            utils.perf.start('main.init');
            main.setupGmFunctions();
            main.registerTampermonkeyMenuCommands();
            // Non-blocking update check (cached)
            main.checkForUserscriptUpdate().catch(() => {});
            state.user.actualTornApiKey = await state.gm.rD_getApiKey();
            const isReady = await main.initializeUserAndApiKey();
            if (isReady) {
                main.initializeDebouncedHandlers();
                await main.initializeScriptLogic();
                main.startPolling();
                main.setupActivityListeners();
            }
            utils.perf.stop('main.init');
        },

        // Centralized note-activity helper so various parts of the app can reset inactivity
        noteActivity: () => {
            try {
                state.script.lastActivityTime = Date.now();
                clearTimeout(state.script.activityTimeoutId);
                if (state.script.currentRefreshInterval !== config.REFRESH_INTERVAL_ACTIVE_MS) {
                    state.script.currentRefreshInterval = config.REFRESH_INTERVAL_ACTIVE_MS;
                    main.startPolling();
                }
                state.script.activityTimeoutId = setTimeout(() => {
                    state.script.currentRefreshInterval = config.REFRESH_INTERVAL_INACTIVE_MS;
                    main.startPolling();
                }, config.ACTIVITY_TIMEOUT_MS);
            } catch (_) { /* non-fatal */ }
        },

        initializeDebouncedHandlers: () => {
            handlers.debouncedFetchGlobalData = utils.debounce(handlers.fetchGlobalData, 500);
            handlers.debouncedDibsTarget = utils.debounce(handlers.dibsTarget, 500);
            handlers.debouncedRemoveDibsForTarget = utils.debounce(handlers.removeDibsForTarget, 500);
            handlers.debouncedHandleMedDealToggle = utils.debounce(handlers.handleMedDealToggle, 500);
            handlers.debouncedSetFactionWarData = utils.debounce(handlers.setFactionWarData, 500);
            handlers.debouncedHandleSaveUserNote = utils.debounce(handlers.handleSaveUserNote, 500);
            handlers.debouncedAssignDibs = utils.debounce(handlers.assignDibs, 500);
            handlers.debouncedFetchRetaliationOpportunities = utils.debounce(handlers.fetchRetaliationOpportunities, 1000);
        },
        // Register Tampermonkey menu commands (non-PDA only)
        registerTampermonkeyMenuCommands: () => {
            if (typeof state.gm.rD_registerMenuCommand !== 'function') return;
            try {
                state.gm.rD_registerMenuCommand('TreeDibs: Set/Update Torn API Key', async () => {
                    const current = await state.gm.rD_getApiKey();
                    const input = prompt('TreeDibsMapper: Enter your Torn API Key:', current || '');
                    if (input && input.trim()) {
                        await state.gm.rD_setValue('torn_api_key', input.trim());
                        state.user.actualTornApiKey = input.trim();
                        ui.showMessageBox('API Key saved. Reloading...', 'info');
                        setTimeout(() => location.reload(), 300);
                    }
                });

                state.gm.rD_registerMenuCommand('TreeDibs: Clear Torn API Key', async () => {
                    if (confirm('Clear the stored Torn API Key? You will be prompted again next time.')) {
                        await state.gm.rD_deleteValue('torn_api_key');
                        state.user.actualTornApiKey = null;
                        ui.showMessageBox('API Key cleared. Reloading...', 'info');
                        setTimeout(() => location.reload(), 300);
                    }
                });

                state.gm.rD_registerMenuCommand('TreeDibs: Open Settings Panel', () => ui.toggleSettingsPopup());
                state.gm.rD_registerMenuCommand('TreeDibs: Refresh Now', () => handlers.debouncedFetchGlobalData());
                state.gm.rD_registerMenuCommand('TreeDibs: Check for Update', () => main.checkForUserscriptUpdate(true));
            } catch (e) {
                console.error('[TDM] Failed to register Tampermonkey menu commands:', e);
            }
        },
        checkForUserscriptUpdate: async (force = false) => {
            try {
                const now = Date.now();
                const lastCheck = storage.get('lastUpdateCheck', 0);
                if (!force && (now - lastCheck) < 6 * 60 * 60 * 1000) return; // 6h throttle
                storage.set('lastUpdateCheck', now);

                const metaText = await utils.httpGetText(config.GREASYFORK.updateMetaUrl);
                if (!metaText) return;
                const m = metaText.match(/@version\s+([^\n\r]+)/);
                const latest = m ? m[1].trim() : null;
                if (!latest) return;
                storage.set('lastKnownLatestVersion', latest);
                if (utils.compareVersions(config.VERSION, latest) < 0) {
                    const go = await ui.showConfirmationBox(`A newer TreeDibsMapper version is available (v${latest}). Update now?`, true);
                    if (go) {
                        try { window.open(config.GREASYFORK.downloadUrl, '_blank'); }
                        catch (_) { location.href = config.GREASYFORK.downloadUrl; }
                    } else {
                        // Provide a persistent toast with clickable link
                        ui.showMessageBox(`Update available: v${latest}. Click to open update URL.`, 'warning', 8000, () => {
                            try { window.open(config.GREASYFORK.downloadUrl, '_blank'); }
                            catch (_) { location.href = config.GREASYFORK.downloadUrl; }
                        });
                    }
                }
            } catch (e) {
                // Silent fail; not critical
            }
        },

        setupGmFunctions: () => {
            state.script.isPDA = (config.PDA_API_KEY_PLACEHOLDER[0] !== '#');
            if (state.script.isPDA) {
                state.gm.rD_setValue = (name, value) => localStorage.setItem(name, value);
                state.gm.rD_getValue = (name, def) => localStorage.getItem(name) ?? def;
                state.gm.rD_deleteValue = (name) => localStorage.removeItem(name);
                state.gm.rD_addStyle = (css) => { const style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); };
                state.gm.rD_getApiKey = async () => config.PDA_API_KEY_PLACEHOLDER;
                if (window.flutter_inappwebview && typeof window.flutter_inappwebview.callHandler === 'function') {
                    state.gm.rD_xmlhttpRequest = (details) => {
                        return new Promise((resolve, reject) => {
                            const { method, url, headers, data: body } = details;
                            const pdaPromise = method.toLowerCase() === "post"
                                ? window.flutter_inappwebview.callHandler('PDA_httpPost', url, headers || {}, body || "")
                                : window.flutter_inappwebview.callHandler('PDA_httpGet', url, headers || {});

                            pdaPromise.then(response => {
                                const responseObj = {
                                    status: response?.status || 200,
                                    statusText: response?.statusText || 'OK',
                                    responseText: response?.responseText || '',
                                    finalUrl: url
                                };
                                if (details.onload) details.onload(responseObj);
                                resolve(responseObj);
                            }).catch(error => {
                                if (details.onerror) details.onerror(error);
                                reject(error);
                            });
                        });
                    };
                 } else {
                    state.gm.rD_xmlhttpRequest = GM_xmlhttpRequest;
                }
            } else {
                state.gm.rD_setValue = GM_setValue;
                state.gm.rD_getValue = GM_getValue;
                state.gm.rD_deleteValue = GM_deleteValue;
                state.gm.rD_addStyle = GM_addStyle;
                state.gm.rD_xmlhttpRequest = GM_xmlhttpRequest;
                state.gm.rD_getApiKey = async () => GM_getValue('torn_api_key', null);
                state.gm.rD_registerMenuCommand = GM_registerMenuCommand;
            }
        },

        initializeUserAndApiKey: async () => {
            // utils.perf.start('initializeUserAndApiKey');
            // Check Key access level and prompt if the level is too low
            const keyInfo = await api.getKeyInfo(state.user.actualTornApiKey);
            state.user.actualTornApiKeyAccess = keyInfo?.info?.access?.level || 0;
            if (keyInfo && keyInfo.info && keyInfo.info.access && keyInfo.info.access.level < 3) {
                ui.showMessageBox("[TDM] Your API Key has insufficient access level. Limited is now needed. Please upgrade your API Key.", "error");
            }
            
            if (!state.user.actualTornApiKey || state.user.actualTornApiKey.trim() === "") {
            
                if (state.script.isPDA) {
                    ui.showMessageBox("PDA API Key placeholder not replaced. Please enter a limited Key.", "error");
                    // utils.perf.stop('initializeUserAndApiKey');
                    return false;
                } else {
                    let userInput = prompt("TreeDibsMapper: Please enter Limited Torn API Key. See userscript for Torn TOS info", '');
                    if (userInput?.trim()) {
                        await state.gm.rD_setValue('torn_api_key', userInput.trim());
                        state.user.actualTornApiKey = userInput.trim();
                        ui.showMessageBox("API Key updated. Reloading...", "info");
                        location.reload();
                        // utils.perf.stop('initializeUserAndApiKey');
                        return false;
                    } else {
                        ui.showMessageBox("No API key provided. Script features will not work.", "warning");
                        // utils.perf.stop('initializeUserAndApiKey');
                        return false;
                    }
                }
            }
            try {
                const tornUser = await api.getTornUser(state.user.actualTornApiKey);
                if (!tornUser)  { 
                    // utils.perf.stop('initializeUserAndApiKey'); 
                    return false; }
                state.user.tornUserObject = tornUser;
                state.user.tornId = tornUser.player_id.toString();
                state.user.tornUsername = tornUser.name;
                state.user.factionId = tornUser.faction?.faction_id.toString() || null;
                storage.updateStateAndStorage('user', state.user);
                // Determine admin rights via backend settings for this faction
                try {
                    const settings = await api.get('getFactionSettings', { factionId: state.user.factionId });
                    state.script.factionSettings = settings;
                    state.script.currentUserPosition = tornUser.faction?.position;
                    const adminRoles = Array.isArray(settings?.adminRoles) ? settings.adminRoles : config.ALLOWED_POSITIONS_FOR_WAR_CONTROLS;
                    state.script.canAdministerMedDeals = !!state.script.currentUserPosition && adminRoles.includes(state.script.currentUserPosition);
                    if (settings && settings.options) {
                        // Optionally map future per-faction options here
                    }
                    if (settings && settings.approved === false) {
                        ui.showMessageBox('Your faction is not approved to use TreeDibsMapper yet. Contact your leader.', 'error');
                        // Return true so the script UI still loads, but avoid admin features implicitly handled by flags
                    }
                } catch (_) { /* fallback silently */ }
                const factionData = await api.getTornFaction(state.user.actualTornApiKey, 'members');
                if (factionData && factionData.members && Array.isArray(factionData.members)) {
                    // Store all member keys from v2 response
                    state.factionMembers = factionData.members.map(member => ({
                        id: member.id,
                        name: member.name,
                        level: member.level,
                        days_in_faction: member.days_in_faction,
                        last_action: member.last_action,
                        status: member.status,
                        revive_setting: member.revive_setting,
                        position: member.position,
                        is_revivable: member.is_revivable,
                        is_on_wall: member.is_on_wall,
                        is_in_oc: member.is_in_oc,
                        has_early_discharge: member.has_early_discharge
                    }));
                }
                // utils.perf.stop('initializeUserAndApiKey');
                return true;
            } catch (error) {
                 ui.showMessageBox(`API Key Error: ${error.message}. Please check your key.`, "error");
                 await state.gm.rD_deleteValue('torn_api_key');
                 // utils.perf.stop('initializeUserAndApiKey');
                 return false;
            }
        },

        initializeScriptLogic: async () => {
            utils.perf.start('initializeScriptLogic');
            state.script.hasProcessedRankedWarTables = false;
            state.script.hasProcessedFactionList = false;

            // if (state.script.canAdministerMedDeals) {      
            // }

            await handlers.fetchGlobalData();
            ui.updatePageContext();
            ui.applyGeneralStyles();
            ui.updateColumnVisibilityStyles();
            if (state.page.isFactionPage || state.page.isAttackPage) ui.createSettingsButton();
            if (state.page.isAttackPage) await ui.injectAttackPageUI();
            if (state.dom.factionListContainer) {
                await ui.processFactionPageMembers(state.dom.factionListContainer);
                state.script.hasProcessedFactionList = true;
                ui.updateFactionPageUI(state.dom.factionListContainer);
            }
            // On SPA hash changes, ensure timers and counters even if fetch is throttled
            ui.updateRetalsButtonCount();
            ui.ensureChainTimer();
            ui.ensureInactivityTimer();
            ui.ensureOpponentStatus();
            handlers.checkOCReminder();
            main.setupMutationObserver();
            utils.perf.stop('initializeScriptLogic');
        },

        startPolling: () => {
            if (state.script.mainRefreshIntervalId) clearInterval(state.script.mainRefreshIntervalId);
            state.script.mainRefreshIntervalId = setInterval(() => {
                handlers.debouncedFetchGlobalData();
                handlers.debouncedFetchRetaliationOpportunities?.();
                handlers.enforceDibsPolicies?.();
            }, state.script.currentRefreshInterval);
        },

        setupMutationObserver: () => {
            if (state.script.mutationObserver) state.script.mutationObserver.disconnect();
            const observerTarget = document.body;
            if (!observerTarget) return;
            state.script.mutationObserver = new MutationObserver(utils.debounce(async (mutations, obs) => {
                ui.updatePageContext();
                if (state.dom.rankwarContainer && !state.script.hasProcessedRankedWarTables) {
                    await ui.processRankedWarTables();
                }
                if (state.dom.factionListContainer && !state.script.hasProcessedFactionList) {
                    await ui.processFactionPageMembers(state.dom.factionListContainer);
                    state.script.hasProcessedFactionList = true;
                    ui.updateFactionPageUI(state.dom.factionListContainer);
                }
                if (state.page.isAttackPage || (state.script.hasProcessedRankedWarTables && state.script.hasProcessedFactionList)) {
                    obs.disconnect();
                }
            }, 200));
            state.script.mutationObserver.observe(observerTarget, { childList: true, subtree: true });
        },

    setupActivityListeners: () => {
        const resetActivityTimer = () => main.noteActivity();
        // Broaden event coverage so clicks and mouse down reset inactivity too
        const evts = ['mousemove', 'mousedown', 'click', 'keydown', 'keyup', 'scroll', 'wheel', 'touchstart', 'touchend'];
        evts.forEach(event => document.addEventListener(event, resetActivityTimer, { passive: true }));
            document.addEventListener('visibilitychange', () => {
                state.script.isWindowActive = !document.hidden;
                if (state.script.isWindowActive) {
                    handlers.debouncedFetchGlobalData();
                    handlers.debouncedFetchRetaliationOpportunities?.();
            resetActivityTimer();
                } else {
                    clearInterval(state.script.mainRefreshIntervalId);
                    clearTimeout(state.script.activityTimeoutId);
                }
            });
            window.addEventListener('hashchange', main.initializeScriptLogic);
        // Initialize timer state immediately
        resetActivityTimer();
        }
    };

    setTimeout(() => {
        ui.updatePageContext();
        if (!state.page.isFactionPage && !state.page.isAttackPage) {
            return;
        }
        console.log(`[TreeDibsMapper] Script execution started (setTimeout). Hardcoded Version: ${config.VERSION}`);
        main.init();
    }, 0);

})();