Purchase Tracker

Track faction armory purchases, deposits, and display case items for reimbursement management

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Purchase Tracker
// @namespace    https://torn.com
// @version      3.0.0
// @description  Track faction armory purchases, deposits, and display case items for reimbursement management
// @author       Oatshead [3487562]
// @match        https://www.torn.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        unsafeWindow
// @connect      api.torn.com
// @connect      script.google.com
// @connect      script.googleusercontent.com
// @connect      *.google.com
// @connect      *.googleusercontent.com
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function () {
    "use strict";

    // ============================================
    // CONFIGURATION
    // ============================================
    const CONFIG = {
        POLL_INTERVAL_MS: 60000,
        LOG_TYPES: {
            ITEM_MARKET_BUY: 1112,
            BAZAAR_BUY: 1225,
            FACTION_DEPOSIT: 6728,
            DISPLAY_ADD: 1302,
            DISPLAY_REMOVE: 1303,
        },
        VERSION: "2.0.0",
        SYNC_MODES: {
            PERSONAL_ONLY: "personal",
            SHARED_ONLY: "shared",
            BOTH: "both",
        },
    };

    // ============================================
    // STORAGE UTILITIES
    // ============================================
    const Storage = {
        get: function (key, defaultValue = null) {
            try {
                if (typeof GM_getValue !== "undefined") {
                    const value = GM_getValue(key, null);
                    return value !== null ? value : defaultValue;
                }
                const stored = localStorage.getItem("purchaseTracker_" + key);
                return stored !== null ? JSON.parse(stored) : defaultValue;
            } catch (e) {
                return defaultValue;
            }
        },
        set: function (key, value) {
            try {
                if (typeof GM_setValue !== "undefined") {
                    GM_setValue(key, value);
                } else {
                    localStorage.setItem("purchaseTracker_" + key, JSON.stringify(value));
                }
            } catch (e) {
                console.error("[PurchaseTracker] Storage.set error:", e);
            }
        },
        delete: function (key) {
            try {
                if (typeof GM_deleteValue !== "undefined") {
                    GM_deleteValue(key);
                } else {
                    localStorage.removeItem("purchaseTracker_" + key);
                }
            } catch (e) {
                console.error("[PurchaseTracker] Storage.delete error:", e);
            }
        },
    };

    // ============================================
    // MIGRATION: Old single whitelist -> split whitelists
    // ============================================
    function migrateWhitelist() {
        const oldWhitelist = Storage.get("whitelist", null);
        if (oldWhitelist !== null && Array.isArray(oldWhitelist) && oldWhitelist.length > 0) {
            console.log("[PurchaseTracker] Migrating old whitelist (" + oldWhitelist.length + " items) to split whitelists");
            const existingPersonal = Storage.get("personalWhitelist", null);
            const existingShared = Storage.get("sharedWhitelist", null);
            if (existingPersonal === null) {
                Storage.set("personalWhitelist", [...oldWhitelist]);
            }
            if (existingShared === null) {
                Storage.set("sharedWhitelist", [...oldWhitelist]);
            }
            Storage.delete("whitelist");
            console.log("[PurchaseTracker] Migration complete");
        } else if (oldWhitelist !== null) {
            // Old key exists but is empty — clean it up
            Storage.delete("whitelist");
        }
    }

    // Run migration before State initializes
    migrateWhitelist();

    // ============================================
    // STATE MANAGEMENT
    // ============================================
    const State = {
        sheetsSectionCollapsed: Storage.get("sheetsSectionCollapsed", false),
        apiKey: Storage.get("apiKey", ""),
        userId: Storage.get("userId", null),
        userName: Storage.get("userName", ""),
        // NEW: Split whitelists
        personalWhitelist: Storage.get("personalWhitelist", []),
        sharedWhitelist: Storage.get("sharedWhitelist", []),
        // NEW: Remember which whitelist user was editing
        lastViewedWhitelist: Storage.get("lastViewedWhitelist", "shared"),
        lastSyncTimestamp: Storage.get("lastSyncTimestamp", 0),
        processedLogIds: Storage.get("processedLogIds", []),
        itemCache: Storage.get("itemCache", {}),
        itemCacheTimestamp: Storage.get("itemCacheTimestamp", 0),
        sellerCache: Storage.get("sellerCache", {}),
        isPolling: false,
        pollIntervalId: null,
        autoSyncEnabled: Storage.get("autoSyncEnabled", false),
        togglePosition: Storage.get("togglePosition", { x: null, y: 100 }),

        personalSheetUrl: Storage.get("personalSheetUrl", ""),
        sharedSheetUrl: Storage.get("sharedSheetUrl", ""),
        syncMode: Storage.get("syncMode", CONFIG.SYNC_MODES.SHARED_ONLY),

        save: function () {
            Storage.set("apiKey", this.apiKey);
            Storage.set("userId", this.userId);
            Storage.set("userName", this.userName);
            Storage.set("personalWhitelist", this.personalWhitelist);
            Storage.set("sharedWhitelist", this.sharedWhitelist);
            Storage.set("lastViewedWhitelist", this.lastViewedWhitelist);
            Storage.set("lastSyncTimestamp", this.lastSyncTimestamp);
            Storage.set("processedLogIds", this.processedLogIds);
            Storage.set("itemCache", this.itemCache);
            Storage.set("itemCacheTimestamp", this.itemCacheTimestamp);
            Storage.set("sellerCache", this.sellerCache);
        },

        saveSheetConfig: function () {
            Storage.set("personalSheetUrl", this.personalSheetUrl);
            Storage.set("sharedSheetUrl", this.sharedSheetUrl);
            Storage.set("syncMode", this.syncMode);
        },

        saveSheetsSectionState: function () {
            Storage.set("sheetsSectionCollapsed", this.sheetsSectionCollapsed);
        },

        saveAutoSync: function () {
            Storage.set("autoSyncEnabled", this.autoSyncEnabled);
        },

        saveTogglePosition: function () {
            Storage.set("togglePosition", this.togglePosition);
        },

        isAuthenticated: function () {
            return !!(this.apiKey && this.userId);
        },

        hasValidSheetConfig: function () {
            switch (this.syncMode) {
                case CONFIG.SYNC_MODES.PERSONAL_ONLY:
                    return !!this.personalSheetUrl;
                case CONFIG.SYNC_MODES.SHARED_ONLY:
                    return !!this.sharedSheetUrl;
                case CONFIG.SYNC_MODES.BOTH:
                    return !!this.personalSheetUrl && !!this.sharedSheetUrl;
                default:
                    return false;
            }
        },

        getActiveSheetUrls: function () {
            const urls = [];
            switch (this.syncMode) {
                case CONFIG.SYNC_MODES.PERSONAL_ONLY:
                    if (this.personalSheetUrl)
                        urls.push({ url: this.personalSheetUrl, type: "personal" });
                    break;
                case CONFIG.SYNC_MODES.SHARED_ONLY:
                    if (this.sharedSheetUrl)
                        urls.push({ url: this.sharedSheetUrl, type: "shared" });
                    break;
                case CONFIG.SYNC_MODES.BOTH:
                    if (this.personalSheetUrl)
                        urls.push({ url: this.personalSheetUrl, type: "personal" });
                    if (this.sharedSheetUrl)
                        urls.push({ url: this.sharedSheetUrl, type: "shared" });
                    break;
            }
            return urls;
        },

        // NEW: Get the whitelist for a specific sheet type
        getWhitelistForSheetType: function (sheetType) {
            if (sheetType === "personal") return this.personalWhitelist;
            if (sheetType === "shared") return this.sharedWhitelist;
            return [];
        },

        // NEW: Get the whitelist the user is currently editing in the UI
        getCurrentWhitelist: function () {
            if (this.syncMode === CONFIG.SYNC_MODES.PERSONAL_ONLY) {
                return this.personalWhitelist;
            }
            if (this.syncMode === CONFIG.SYNC_MODES.SHARED_ONLY) {
                return this.sharedWhitelist;
            }
            // "both" mode: use lastViewedWhitelist to determine which one
            if (this.lastViewedWhitelist === "personal") {
                return this.personalWhitelist;
            }
            return this.sharedWhitelist;
        },

        // NEW: Get the type string of the currently-edited whitelist
        getCurrentWhitelistType: function () {
            if (this.syncMode === CONFIG.SYNC_MODES.PERSONAL_ONLY) return "personal";
            if (this.syncMode === CONFIG.SYNC_MODES.SHARED_ONLY) return "shared";
            return this.lastViewedWhitelist || "shared";
        },

        clearProcessedLogs: function () {
            if (this.processedLogIds.length > 1000) {
                this.processedLogIds = this.processedLogIds.slice(-500);
                this.save();
            }
        },
    };

    // ============================================
    // DEVICE DETECTION
    // ============================================
    const Device = {
        isTornPDA: function () {
            return (
                typeof window.PDA !== "undefined" ||
                navigator.userAgent.includes("TornPDA") ||
                /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
                    navigator.userAgent,
                )
            );
        },
        isMobile: function () {
            return window.innerWidth <= 768 || this.isTornPDA();
        },
    };

    // ============================================
    // API UTILITIES
    // ============================================
    const API = {
        isTornPDA: function () {
            return typeof GM_xmlhttpRequest === "undefined";
        },

        tornRequest: async function (endpoint, selections) {
            if (!State.apiKey) {
                throw new Error("API key not set");
            }
            const url = `https://api.torn.com/${endpoint}?selections=${selections}&key=${State.apiKey}`;

            if (typeof GM_xmlhttpRequest === "undefined") {
                return this.fetchRequest(url);
            }

            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: url,
                    responseType: "json",
                    onload: function (response) {
                        try {
                            let data = response.response;
                            if (!data && response.responseText) {
                                data = JSON.parse(response.responseText);
                            }
                            if (!data) {
                                reject(new Error("Empty response from Torn API"));
                                return;
                            }
                            if (data.error) {
                                reject(new Error(`Torn API Error ${data.error.code}: ${data.error.error}`));
                            } else {
                                resolve(data);
                            }
                        } catch (e) {
                            reject(new Error("Failed to parse Torn API response: " + e.message));
                        }
                    },
                    onerror: function () {
                        reject(new Error("Network error contacting Torn API"));
                    },
                    ontimeout: function () {
                        reject(new Error("Torn API request timed out"));
                    },
                });
            });
        },

        fetchRequest: async function (url) {
            try {
                const response = await fetch(url);
                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
                }
                const text = await response.text();
                if (!text) {
                    throw new Error("Empty response from Torn API");
                }
                const data = JSON.parse(text);
                if (data.error) {
                    throw new Error(`Torn API Error ${data.error.code}: ${data.error.error}`);
                }
                return data;
            } catch (e) {
                throw e;
            }
        },

        // Send request to a specific sheet URL
        sheetsRequest: async function (sheetUrl, action, data) {
            const payload = {
                action: action,
                userId: State.userId,
                userName: State.userName,
                data: data,
            };

            if (typeof GM_xmlhttpRequest === "undefined") {
                return this.fetchPostRequest(sheetUrl, payload);
            }

            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "POST",
                    url: sheetUrl,
                    headers: { "Content-Type": "text/plain" },
                    data: JSON.stringify(payload),
                    anonymous: false,
                    onload: function (response) {
                        try {
                            const text = response.responseText || "";
                            const status = response.status;

                            console.log("[PurchaseTracker] Sheet response status:", status);
                            console.log("[PurchaseTracker] Sheet response (first 300 chars):", text.substring(0, 300));

                            if (text.trim().startsWith("<") || text.trim().startsWith("<!DOCTYPE")) {
                                console.error("[PurchaseTracker] Got HTML instead of JSON");
                                reject(new Error("Sheet returned HTML instead of JSON. The request was not processed. Try updating Tampermonkey."));
                                return;
                            }

                            if (!text.trim()) {
                                console.error("[PurchaseTracker] Empty response from sheet");
                                reject(new Error("Empty response from sheet — data may not have been saved"));
                                return;
                            }

                            const responseData = JSON.parse(text);

                            if (responseData.success === false) {
                                console.error("[PurchaseTracker] Sheet returned error:", responseData.error);
                                reject(new Error(responseData.error || "Sheet reported failure"));
                                return;
                            }

                            if (responseData.message && responseData.message.includes("Web App is running")) {
                                console.error("[PurchaseTracker] Got doGet response — POST was converted to GET");
                                reject(new Error("Request was redirected as GET instead of POST. Data was not saved."));
                                return;
                            }

                            console.log("[PurchaseTracker] Sheet response valid:", JSON.stringify(responseData));
                            resolve(responseData);
                        } catch (e) {
                            console.error("[PurchaseTracker] Failed to parse sheet response:", e.message);
                            reject(new Error("Failed to parse sheet response: " + e.message));
                        }
                    },
                    onerror: function (error) {
                        console.error("[PurchaseTracker] Sheet request network error:", error);
                        reject(new Error("Network error contacting Sheets"));
                    },
                    ontimeout: function () {
                        reject(new Error("Sheet request timed out"));
                    },
                });
            });
        },

        // NEW: Send to a single specific sheet (replaces sendToAllSheets for sync)
        sendToSheet: async function (sheetUrl, action, data) {
            return await this.sheetsRequest(sheetUrl, action, data);
        },

        fetchPostRequest: async function (url, body) {
            try {
                const response = await fetch(url, {
                    method: "POST",
                    headers: { "Content-Type": "text/plain" },
                    body: JSON.stringify(body),
                    redirect: "follow",
                });

                const text = await response.text();

                if (!text || !text.trim()) {
                    throw new Error("Empty response from sheet");
                }

                if (text.trim().startsWith("<") || text.trim().startsWith("<!DOCTYPE")) {
                    console.log("[PurchaseTracker] Got HTML redirect, attempting manual redirect...");
                    return await this.handleRedirectManually(url, body, text);
                }

                const data = JSON.parse(text);

                if (data.success === false) {
                    throw new Error(data.error || "Sheet reported failure");
                }

                if (data.message && data.message.includes("Web App is running")) {
                    console.warn("[PurchaseTracker] Got doGet response — POST was redirected as GET");
                    return {
                        success: true,
                        added: -1,
                        warning: "Redirect detected — data may have been saved. Verify in sheet.",
                        redirected: true,
                    };
                }

                return data;
            } catch (e) {
                if (e.message && (e.message.includes("redirect") || e.message.includes("HTML"))) {
                    console.warn("[PurchaseTracker] TornPDA redirect issue:", e.message);
                    return {
                        success: true,
                        added: -1,
                        warning: e.message,
                        redirected: true,
                    };
                }
                throw e;
            }
        },

        handleRedirectManually: async function (originalUrl, body, htmlResponse) {
            const match = htmlResponse.match(/https:\/\/script\.googleusercontent\.com[^"'\s<>]+/);
            if (match) {
                console.log("[PurchaseTracker] Found redirect URL, POSTing directly...");
                try {
                    const response = await fetch(match[0], {
                        method: "POST",
                        headers: { "Content-Type": "text/plain" },
                        body: JSON.stringify(body),
                    });
                    const text = await response.text();
                    if (text && !text.trim().startsWith("<")) {
                        return JSON.parse(text);
                    }
                } catch (e) {
                    console.warn("[PurchaseTracker] Manual redirect failed:", e.message);
                }
            }

            return {
                success: true,
                added: -1,
                warning: "Could not confirm data was saved (redirect issue)",
                redirected: true,
            };
        },

        testSheetUrl: async function (url) {
            if (typeof GM_xmlhttpRequest !== "undefined") {
                return new Promise((resolve) => {
                    GM_xmlhttpRequest({
                        method: "GET",
                        url: url,
                        timeout: 10000,
                        onload: function (response) {
                            try {
                                const text = response.responseText;
                                console.log("[PurchaseTracker] Test response:", text.substring(0, 200));

                                if (text.includes('"success"') || text.includes('"configured"') || text.includes('"message"')) {
                                    const data = JSON.parse(text);
                                    if (data.success === false) {
                                        resolve({ valid: false, error: data.error || data.message || "Sheet returned an error" });
                                    } else {
                                        resolve({ valid: true, data });
                                    }
                                } else if (text.trim().startsWith("<")) {
                                    resolve({ valid: false, error: "Received HTML instead of JSON. Check deployment settings." });
                                } else {
                                    resolve({ valid: false, error: "Unexpected response format" });
                                }
                            } catch (e) {
                                console.error("[PurchaseTracker] Test parse error:", e);
                                resolve({ valid: false, error: "Failed to parse response: " + e.message });
                            }
                        },
                        onerror: function (error) {
                            console.error("[PurchaseTracker] Test connection error:", error);
                            resolve({ valid: false, error: "Connection failed. Check the URL." });
                        },
                        ontimeout: function () {
                            resolve({ valid: false, error: "Connection timed out" });
                        },
                    });
                });
            } else {
                try {
                    const response = await fetch(url);
                    const text = await response.text();
                    if (text.includes('"success"') || text.includes('"configured"')) {
                        const data = JSON.parse(text);
                        if (data.success === false) {
                            return { valid: false, error: data.error || data.message || "Sheet returned an error" };
                        }
                        return { valid: true, data };
                    }
                    return { valid: false, error: "Invalid response from sheet" };
                } catch (e) {
                    return { valid: false, error: e.message };
                }
            }
        },
    };

    // ============================================
    // AUTHENTICATION
    // ============================================
    const Auth = {
        validateApiKey: async function (apiKey) {
            State.apiKey = apiKey;
            try {
                const data = await API.tornRequest("user", "basic");
                State.userId = data.player_id;
                State.userName = data.name;
                State.save();
                return { success: true, user: data };
            } catch (e) {
                State.apiKey = "";
                State.save();
                return { success: false, error: e.message };
            }
        },
    };

    // ============================================
    // ITEM CACHE MANAGEMENT
    // ============================================
    const ItemCache = {
        CACHE_DURATION: 24 * 60 * 60 * 1000,

        needsRefresh: function () {
            return (
                !State.itemCacheTimestamp ||
                Date.now() - State.itemCacheTimestamp > this.CACHE_DURATION ||
                Object.keys(State.itemCache).length === 0
            );
        },

        refresh: async function () {
            try {
                UI.setStatus("Refreshing item cache...");
                const data = await API.tornRequest("torn", "items");
                if (data.items) {
                    State.itemCache = {};
                    for (const [id, item] of Object.entries(data.items)) {
                        State.itemCache[id] = { name: item.name, type: item.type };
                    }
                    State.itemCacheTimestamp = Date.now();
                    State.save();
                    UI.setStatus("Item cache updated (" + Object.keys(State.itemCache).length + " items)", "success");
                    return true;
                }
            } catch (e) {
                UI.setStatus("Failed to refresh item cache: " + e.message, "error");
            }
            return false;
        },

        getItemName: function (itemId) {
            const item = State.itemCache[String(itemId)];
            return item ? item.name : `Item #${itemId}`;
        },

        getItemType: function (itemId) {
            const item = State.itemCache[String(itemId)];
            return item ? item.type : "Unknown";
        },

        searchByName: function (searchTerm) {
            const searchLower = searchTerm.toLowerCase().trim();
            const results = [];

            for (const [id, item] of Object.entries(State.itemCache)) {
                if (item.name.toLowerCase().includes(searchLower)) {
                    results.push({ id: parseInt(id), name: item.name, type: item.type });
                }
            }

            results.sort((a, b) => {
                const aExact = a.name.toLowerCase() === searchLower;
                const bExact = b.name.toLowerCase() === searchLower;
                if (aExact && !bExact) return -1;
                if (bExact && !aExact) return 1;
                return a.name.localeCompare(b.name);
            });

            return results;
        },
    };

    // ============================================
    // SELLER CACHE
    // ============================================
    const SellerCache = {
        getSellerName: async function (sellerId) {
            if (!sellerId) return "Unknown";
            if (State.sellerCache[sellerId]) {
                return State.sellerCache[sellerId];
            }
            try {
                const data = await API.tornRequest(`user/${sellerId}`, "basic");
                if (data.name) {
                    State.sellerCache[sellerId] = data.name;
                    State.save();
                    return data.name;
                }
            } catch (e) {
                console.error("[PurchaseTracker] Failed to fetch seller name:", e);
            }
            return `User #${sellerId}`;
        },
    };

    // ============================================
    // SYNC LOGIC (REWRITTEN for split whitelists)
    // ============================================
    const Sync = {
        isRunning: false,

        // Extract raw purchase data from a log entry WITHOUT whitelist filtering
        extractRawPurchase: async function (logId, entry) {
            const data = entry.data;
            if (!data || !data.items || data.items.length === 0) return null;

            const sellerName = await SellerCache.getSellerName(data.seller);

            const records = [];
            for (const item of data.items) {
                records.push({
                    logId: logId,
                    timestamp: entry.timestamp,
                    source: entry.log === CONFIG.LOG_TYPES.ITEM_MARKET_BUY ? "ItemMarket" : "Bazaar",
                    itemId: item.id,
                    itemName: ItemCache.getItemName(item.id),
                    itemType: ItemCache.getItemType(item.id),
                    quantity: item.qty,
                    unitPrice: data.cost_each,
                    totalSpent: item.qty * data.cost_each,
                    sellerId: data.seller,
                    sellerName: sellerName,
                });
            }

            return records;
        },

        // Extract raw deposit data from a log entry WITHOUT whitelist filtering
        extractRawDeposit: function (logId, entry) {
            const data = entry.data;
            if (!data || !data.items || data.items.length === 0) return null;

            const records = [];
            for (const item of data.items) {
                records.push({
                    logId: logId,
                    timestamp: entry.timestamp,
                    factionId: data.faction,
                    itemId: item.id,
                    itemName: ItemCache.getItemName(item.id),
                    quantity: item.qty,
                });
            }

            return records;
        },

        // Filter purchase records by a specific whitelist
        filterPurchasesByWhitelist: function (rawPurchases, whitelist) {
            if (whitelist.length === 0) return rawPurchases; // empty = track all
            return rawPurchases.filter((p) => whitelist.includes(p.itemId));
        },

        // Filter deposit records by a specific whitelist
        filterDepositsByWhitelist: function (rawDeposits, whitelist) {
            if (whitelist.length === 0) return rawDeposits; // empty = track all
            return rawDeposits.filter((d) => whitelist.includes(d.itemId));
        },

        // Flatten purchases into the format the sheet expects
        flattenPurchases: function (purchases) {
            return purchases.map((item) => ({
                logId: item.logId,
                timestamp: item.timestamp,
                buyerId: State.userId,
                buyerName: State.userName,
                source: item.source,
                itemId: item.itemId,
                itemName: item.itemName,
                itemType: item.itemType,
                qty: item.quantity,
                unitPrice: item.unitPrice,
                totalSpent: item.totalSpent,
                sellerId: item.sellerId,
                sellerName: item.sellerName,
            }));
        },

        // Flatten deposits into the format the sheet expects
        flattenDeposits: function (deposits) {
            return deposits.map((item) => ({
                depositLogId: item.logId,
                timestamp: item.timestamp,
                depositorId: State.userId,
                depositorName: State.userName,
                factionId: item.factionId,
                itemId: item.itemId,
                itemName: item.itemName,
                qty: item.quantity,
            }));
        },

        run: async function () {
            if (this.isRunning) {
                console.log("[PurchaseTracker] Sync already running, skipping");
                return;
            }

            if (!State.isAuthenticated()) {
                UI.setStatus("Not authenticated. Please enter your API key.", "error");
                return;
            }

            if (!State.hasValidSheetConfig()) {
                UI.setStatus("Please configure at least one sheet URL", "error");
                return;
            }

            this.isRunning = true;
            UI.setStatus("Syncing...");

            try {
                if (ItemCache.needsRefresh()) {
                    await ItemCache.refresh();
                }

                const logsData = await API.tornRequest("user", "log");
                const logs = logsData.log || {};

                // ---- PHASE 1: Extract ALL raw data (no whitelist filtering) ----
                const rawPurchases = []; // Each element is an array of records from one log entry
                const rawDeposits = [];  // Each element is an array of records from one log entry
                const relevantLogIds = new Set(); // All log IDs that had purchase or deposit data

                for (const [logId, entry] of Object.entries(logs)) {
                    if (State.processedLogIds.includes(logId)) continue;
                    if (entry.timestamp < State.lastSyncTimestamp - 300) continue;

                    if (
                        entry.log === CONFIG.LOG_TYPES.ITEM_MARKET_BUY ||
                        entry.log === CONFIG.LOG_TYPES.BAZAAR_BUY
                    ) {
                        const records = await this.extractRawPurchase(logId, entry);
                        if (records && records.length > 0) {
                            rawPurchases.push(...records);
                            relevantLogIds.add(logId);
                        }
                    }

                    if (entry.log === CONFIG.LOG_TYPES.FACTION_DEPOSIT) {
                        const records = this.extractRawDeposit(logId, entry);
                        if (records && records.length > 0) {
                            rawDeposits.push(...records);
                            relevantLogIds.add(logId);
                        }
                    }
                }

                if (relevantLogIds.size === 0) {
                    State.lastSyncTimestamp = Math.floor(Date.now() / 1000);
                    State.save();
                    const timeStr = new Date().toLocaleTimeString();
                    UI.setStatus(`No new events (${timeStr})`, "success");
                    this.isRunning = false;
                    return;
                }

                // ---- PHASE 2: Build per-sheet payloads using respective whitelists ----
                const activeSheets = State.getActiveSheetUrls();

                // Track success/failure per sheet type, and which logIds each sheet touches
                const sheetResults = {}; // { "personal": { success: true/false }, "shared": { ... } }
                const logIdToSheetTypes = {}; // { "logId123": Set(["personal", "shared"]) }
                let totalSent = 0;
                let failCount = 0;

                // Process each sheet SEQUENTIALLY
                for (const { url, type } of activeSheets) {
                    const whitelist = State.getWhitelistForSheetType(type);

                    // Filter raw data through this sheet's whitelist
                    const filteredPurchases = this.filterPurchasesByWhitelist(rawPurchases, whitelist);
                    const filteredDeposits = this.filterDepositsByWhitelist(rawDeposits, whitelist);

                    // Track which logIds this sheet cares about
                    const sheetLogIds = new Set();
                    for (const p of filteredPurchases) sheetLogIds.add(p.logId);
                    for (const d of filteredDeposits) sheetLogIds.add(d.logId);

                    // Register this sheet type against its logIds
                    for (const lid of sheetLogIds) {
                        if (!logIdToSheetTypes[lid]) logIdToSheetTypes[lid] = new Set();
                        logIdToSheetTypes[lid].add(type);
                    }

                    if (sheetLogIds.size === 0) {
                        // No items for this sheet after filtering
                        console.log(`[PurchaseTracker] No relevant items for ${type} sheet after whitelist filter`);
                        sheetResults[type] = { success: true, sent: 0 };
                        continue;
                    }

                    let sheetSuccess = true;

                    // Send purchases to this sheet
                    if (filteredPurchases.length > 0) {
                        const flatPurchases = this.flattenPurchases(filteredPurchases);
                        UI.setStatus(`Sending ${flatPurchases.length} purchase(s) to ${type} sheet...`);

                        try {
                            const result = await API.sendToSheet(url, "addPurchases", flatPurchases);
                            if (result && result.success === true && (result.added !== undefined || result.skipped !== undefined)) {
                                totalSent += flatPurchases.length;
                                if (result.redirected) {
                                    console.warn(`[PurchaseTracker] ${type} sheet: redirect detected, data may have been saved`);
                                } else {
                                    console.log(`[PurchaseTracker] ${type} sheet confirmed purchases: added=${result.added}, skipped=${result.skipped || 0}`);
                                }
                            } else {
                                sheetSuccess = false;
                                failCount++;
                                console.error(`[PurchaseTracker] ${type} sheet purchase response invalid:`, result);
                            }
                        } catch (e) {
                            sheetSuccess = false;
                            failCount++;
                            console.error(`[PurchaseTracker] Error sending purchases to ${type} sheet:`, e.message);
                        }
                    }

                    // Send deposits to this sheet
                    if (filteredDeposits.length > 0) {
                        const flatDeposits = this.flattenDeposits(filteredDeposits);
                        UI.setStatus(`Sending ${flatDeposits.length} deposit(s) to ${type} sheet...`);

                        try {
                            const result = await API.sendToSheet(url, "processDeposits", flatDeposits);
                            if (result && result.success === true) {
                                totalSent += flatDeposits.length;
                                console.log(`[PurchaseTracker] ${type} sheet confirmed deposits`);
                            } else {
                                sheetSuccess = false;
                                failCount++;
                                console.error(`[PurchaseTracker] ${type} sheet deposit response invalid:`, result);
                            }
                        } catch (e) {
                            sheetSuccess = false;
                            failCount++;
                            console.error(`[PurchaseTracker] Error sending deposits to ${type} sheet:`, e.message);
                        }
                    }

                    sheetResults[type] = { success: sheetSuccess, sent: filteredPurchases.length + filteredDeposits.length };
                }

                // ---- PHASE 3: Mark logIds as processed only if ALL required sheets succeeded ----
                let markedCount = 0;
                let retriedCount = 0;

                for (const [logId, requiredSheetTypes] of Object.entries(logIdToSheetTypes)) {
                    let allSucceeded = true;
                    for (const st of requiredSheetTypes) {
                        if (!sheetResults[st] || !sheetResults[st].success) {
                            allSucceeded = false;
                            break;
                        }
                    }

                    if (allSucceeded) {
                        if (!State.processedLogIds.includes(logId)) {
                            State.processedLogIds.push(logId);
                            markedCount++;
                        }
                    } else {
                        retriedCount++;
                        console.warn(`[PurchaseTracker] Log ${logId} NOT marked as processed — will retry next sync`);
                    }
                }

                // Also mark logIds that had data but matched NO whitelist on any sheet
                // These are truly irrelevant and should be marked to avoid re-processing
                for (const logId of relevantLogIds) {
                    if (!logIdToSheetTypes[logId]) {
                        // This log had raw data but nothing passed any whitelist filter
                        if (!State.processedLogIds.includes(logId)) {
                            State.processedLogIds.push(logId);
                        }
                    }
                }

                console.log(`[PurchaseTracker] Marked ${markedCount} logIds as processed, ${retriedCount} will retry`);

                State.lastSyncTimestamp = Math.floor(Date.now() / 1000);
                State.clearProcessedLogs();
                State.save();

                const timeStr = new Date().toLocaleTimeString();
                const sheetInfo = `(${activeSheets.length} sheet${activeSheets.length > 1 ? "s" : ""})`;

                if (failCount > 0) {
                    UI.setStatus(
                        `Synced with ${failCount} error(s) ${sheetInfo} — ${retriedCount} log(s) will retry. Check console (F12)`,
                        "error",
                    );
                } else {
                    UI.setStatus(
                        totalSent > 0
                            ? `Synced ${totalSent} item(s) ${sheetInfo} at ${timeStr}`
                            : `No whitelisted items to sync (${timeStr})`,
                        "success",
                    );
                }
            } catch (e) {
                console.error("[PurchaseTracker] Sync error:", e);
                UI.setStatus("Sync failed: " + e.message, "error");
            } finally {
                this.isRunning = false;
            }
        },

        // NEW: Display case sync with split payloads
        syncDisplayCase: async function () {
            if (!State.isAuthenticated()) {
                UI.setStatus("Not authenticated", "error");
                return;
            }

            if (!State.hasValidSheetConfig()) {
                UI.setStatus("Please configure at least one sheet URL", "error");
                return;
            }

            UI.setStatus("Syncing display case...");

            try {
                const data = await API.tornRequest("user", "display");
                if (!data.display || data.display.length === 0) {
                    UI.setStatus("Display case is empty", "success");
                    return;
                }

                // Build raw display items (no filtering)
                const rawDisplayItems = data.display.map((item) => ({
                    itemId: item.ID,
                    itemName: item.name,
                    itemType: item.type,
                    quantity: item.quantity,
                    marketPrice: item.market_price,
                }));

                const activeSheets = State.getActiveSheetUrls();
                let successCount = 0;
                let totalItems = 0;

                // Send to each sheet sequentially with its own whitelist filter
                for (const { url, type } of activeSheets) {
                    const whitelist = State.getWhitelistForSheetType(type);

                    const filteredItems = whitelist.length === 0
                        ? rawDisplayItems
                        : rawDisplayItems.filter((item) => whitelist.includes(item.itemId));

                    if (filteredItems.length === 0) {
                        console.log(`[PurchaseTracker] No whitelisted display items for ${type} sheet`);
                        successCount++; // Nothing to send is still a success
                        continue;
                    }

                    try {
                        UI.setStatus(`Sending ${filteredItems.length} display item(s) to ${type} sheet...`);
                        const result = await API.sendToSheet(url, "updateDisplayCase", filteredItems);
                        if (result && result.success) {
                            successCount++;
                            totalItems += filteredItems.length;
                            console.log(`[PurchaseTracker] ${type} sheet display case updated`);
                        } else {
                            console.error(`[PurchaseTracker] ${type} sheet display case update failed:`, result);
                        }
                    } catch (e) {
                        console.error(`[PurchaseTracker] Error sending display case to ${type} sheet:`, e.message);
                    }
                }

                if (successCount === activeSheets.length) {
                    UI.setStatus(
                        `Display case synced to ${successCount} sheet(s): ${totalItems} item(s)`,
                        "success",
                    );
                } else {
                    UI.setStatus(
                        `Display case: ${successCount}/${activeSheets.length} sheets updated`,
                        "error",
                    );
                }
            } catch (e) {
                UI.setStatus("Display case sync failed: " + e.message, "error");
            }
        },

        startPolling: function () {
            if (State.pollIntervalId) {
                this.stopPolling();
            }
            State.isPolling = true;
            State.autoSyncEnabled = true;
            State.saveAutoSync();
            State.pollIntervalId = setInterval(() => this.run(), CONFIG.POLL_INTERVAL_MS);
            UI.updatePollingButton();
            UI.updateToggleIndicator();
        },

        stopPolling: function () {
            if (State.pollIntervalId) {
                clearInterval(State.pollIntervalId);
                State.pollIntervalId = null;
            }
            State.isPolling = false;
            State.autoSyncEnabled = false;
            State.saveAutoSync();
            UI.updatePollingButton();
            UI.updateToggleIndicator();
        },

        resumePollingIfEnabled: function () {
            if (State.autoSyncEnabled && State.isAuthenticated() && State.hasValidSheetConfig()) {
                console.log("[PurchaseTracker] Resuming auto-sync from previous session");
                this.startPolling();
                this.run();
            }
        },
    };

    // ============================================
    // USER INTERFACE
    // ============================================
    const UI = {
        panel: null,
        statusEl: null,
        toggleBtn: null,
        isDragging: false,
        dragStartX: 0,
        dragStartY: 0,
        dragStartPosX: 0,
        dragStartPosY: 0,
        hasMoved: false,

        init: function () {
            this.addStyles();
            this.createPanel();
            this.createToggleButton();

            if (typeof GM_registerMenuCommand !== "undefined") {
                GM_registerMenuCommand("Open Purchase Tracker", () => this.togglePanel());
            }
        },

        addStyles: function () {
            const isMobile = Device.isMobile();

            const css = `
                #purchase-tracker-toggle {
                    position: fixed;
                    z-index: 999999;
                    background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
                    color: #fff;
                    padding: ${isMobile ? "6px 10px" : "8px 12px"};
                    border-radius: 6px;
                    cursor: ${isMobile ? "pointer" : "move"};
                    font-family: Arial, sans-serif;
                    font-size: ${isMobile ? "10px" : "11px"};
                    box-shadow: 0 2px 8px rgba(0,0,0,0.3);
                    border: 1px solid #0f3460;
                    transition: box-shadow 0.2s ease;
                    user-select: none;
                    touch-action: none;
                    white-space: nowrap;
                }
                #purchase-tracker-toggle:hover {
                    box-shadow: 0 4px 12px rgba(0,0,0,0.4);
                }
                #purchase-tracker-toggle.dragging {
                    opacity: 0.8;
                    cursor: grabbing;
                }
                #purchase-tracker-toggle .pt-sync-indicator {
                    display: inline-block;
                    width: 6px;
                    height: 6px;
                    border-radius: 50%;
                    margin-left: 5px;
                    background: #666;
                }
                #purchase-tracker-toggle .pt-sync-indicator.active {
                    background: #2ecc71;
                    animation: pulse 2s infinite;
                }
                @keyframes pulse {
                    0%, 100% { opacity: 1; }
                    50% { opacity: 0.5; }
                }
                #purchase-tracker-panel {
                    position: fixed;
                    top: 50px;
                    right: 20px;
                    width: ${isMobile ? "90vw" : "420px"};
                    max-width: 420px;
                    max-height: 85vh;
                    z-index: 999998;
                    background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
                    color: #e6e6e6;
                    border-radius: 12px;
                    box-shadow: 0 8px 32px rgba(0,0,0,0.4);
                    font-family: Arial, sans-serif;
                    font-size: 13px;
                    display: none;
                    overflow: hidden;
                    border: 1px solid #0f3460;
                }
                #purchase-tracker-panel.visible {
                    display: block;
                }
                .pt-header {
                    background: linear-gradient(135deg, #0f3460 0%, #1a1a2e 100%);
                    padding: 15px;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    border-bottom: 1px solid #0f3460;
                }
                .pt-header h3 {
                    margin: 0;
                    font-size: 16px;
                    color: #fff;
                }
                .pt-close {
                    background: none;
                    border: none;
                    color: #e94560;
                    font-size: 20px;
                    cursor: pointer;
                    padding: 0 5px;
                }
                .pt-body {
                    padding: 15px;
                    max-height: calc(85vh - 60px);
                    overflow-y: auto;
                }
                .pt-section {
                    margin-bottom: 15px;
                    background: rgba(255,255,255,0.03);
                    border-radius: 8px;
                    padding: 12px;
                }
                .pt-section-title {
                    font-weight: bold;
                    margin-bottom: 10px;
                    color: #e94560;
                    font-size: 12px;
                    text-transform: uppercase;
                    letter-spacing: 1px;
                    cursor: pointer;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                }
                .pt-section-title .pt-toggle-icon {
                    transition: transform 0.3s;
                }
                .pt-section-title.collapsed .pt-toggle-icon {
                    transform: rotate(-90deg);
                }
                .pt-section-content {
                    overflow: hidden;
                    transition: max-height 0.3s ease;
                }
                .pt-section-content.collapsed {
                    max-height: 0 !important;
                    padding: 0;
                    margin: 0;
                }
                .pt-input-group {
                    margin-bottom: 10px;
                }
                .pt-input-group label {
                    display: block;
                    margin-bottom: 5px;
                    color: #aaa;
                    font-size: 11px;
                }
                .pt-input-group input[type="text"],
                .pt-input-group input[type="password"] {
                    width: 100%;
                    padding: 10px;
                    border: 1px solid #0f3460;
                    border-radius: 6px;
                    background: rgba(0,0,0,0.3);
                    color: #fff;
                    font-size: 13px;
                    box-sizing: border-box;
                }
                .pt-input-group input:focus {
                    outline: none;
                    border-color: #e94560;
                }
                .pt-input-group input:disabled {
                    opacity: 0.6;
                    cursor: not-allowed;
                }
                .pt-input-group select {
                    width: 100%;
                    padding: 10px;
                    border: 1px solid #0f3460;
                    border-radius: 6px;
                    background: rgba(0,0,0,0.3);
                    color: #fff;
                    font-size: 13px;
                    box-sizing: border-box;
                }
                .pt-input-group select:focus {
                    outline: none;
                    border-color: #e94560;
                }
                .pt-btn {
                    padding: 10px 16px;
                    border: none;
                    border-radius: 6px;
                    cursor: pointer;
                    font-size: 12px;
                    font-weight: bold;
                    transition: all 0.3s ease;
                    margin-right: 8px;
                    margin-bottom: 8px;
                }
                .pt-btn-primary {
                    background: linear-gradient(135deg, #e94560 0%, #c73e54 100%);
                    color: #fff;
                }
                .pt-btn-primary:hover {
                    background: linear-gradient(135deg, #c73e54 0%, #a33547 100%);
                }
                .pt-btn-secondary {
                    background: linear-gradient(135deg, #0f3460 0%, #16213e 100%);
                    color: #fff;
                    border: 1px solid #0f3460;
                }
                .pt-btn-secondary:hover {
                    background: linear-gradient(135deg, #16213e 0%, #1a1a2e 100%);
                }
                .pt-btn-success {
                    background: linear-gradient(135deg, #2ecc71 0%, #27ae60 100%);
                    color: #fff;
                }
                .pt-btn-success:hover {
                    background: linear-gradient(135deg, #27ae60 0%, #1e8449 100%);
                }
                .pt-btn-danger {
                    background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
                    color: #fff;
                }
                .pt-btn-danger:hover {
                    background: linear-gradient(135deg, #c0392b 0%, #a93226 100%);
                }
                .pt-btn-small {
                    padding: 6px 12px;
                    font-size: 11px;
                }
                .pt-status {
                    background: rgba(0,0,0,0.3);
                    padding: 10px;
                    border-radius: 6px;
                    font-size: 12px;
                    color: #aaa;
                    margin-top: 10px;
                    border-left: 3px solid #e94560;
                }
                .pt-status.success {
                    border-left-color: #2ecc71;
                    color: #2ecc71;
                }
                .pt-status.error {
                    border-left-color: #e74c3c;
                    color: #e74c3c;
                }
                .pt-whitelist {
                    max-height: 150px;
                    overflow-y: auto;
                    background: rgba(0,0,0,0.2);
                    border-radius: 6px;
                    padding: 8px;
                    margin-top: 10px;
                }
                .pt-whitelist-item {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    padding: 6px 8px;
                    background: rgba(255,255,255,0.05);
                    border-radius: 4px;
                    margin-bottom: 4px;
                }
                .pt-whitelist-item:last-child {
                    margin-bottom: 0;
                }
                .pt-whitelist-item span {
                    flex: 1;
                    font-size: 12px;
                }
                .pt-whitelist-item button {
                    background: #e74c3c;
                    border: none;
                    color: #fff;
                    padding: 3px 8px;
                    border-radius: 3px;
                    cursor: pointer;
                    font-size: 10px;
                }
                .pt-whitelist-item button:hover {
                    background: #c0392b;
                }
                .pt-info {
                    font-size: 11px;
                    color: #888;
                    margin-top: 5px;
                    margin-bottom: 10px;
                }
                .pt-user-info {
                    background: rgba(46, 204, 113, 0.15);
                    border: 1px solid rgba(46, 204, 113, 0.4);
                    border-radius: 6px;
                    padding: 10px;
                    margin-bottom: 10px;
                    display: flex;
                    align-items: center;
                    gap: 8px;
                }
                .pt-user-info .pt-check {
                    color: #2ecc71;
                    font-size: 18px;
                }
                .pt-user-info strong {
                    color: #2ecc71;
                }
                .pt-btn-group {
                    display: flex;
                    flex-wrap: wrap;
                }
                .pt-search-results {
                    background: rgba(0,0,0,0.3);
                    border-radius: 6px;
                    max-height: 120px;
                    overflow-y: auto;
                    margin-top: 5px;
                    display: none;
                }
                .pt-search-results.visible {
                    display: block;
                }
                .pt-search-result {
                    padding: 8px 10px;
                    cursor: pointer;
                    border-bottom: 1px solid rgba(255,255,255,0.1);
                    font-size: 12px;
                }
                .pt-search-result:hover {
                    background: rgba(233, 69, 96, 0.2);
                }
                .pt-search-result:last-child {
                    border-bottom: none;
                }
                .pt-search-result .pt-item-type {
                    color: #888;
                    font-size: 10px;
                }
                .pt-sheet-status {
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    padding: 8px;
                    border-radius: 4px;
                    margin-top: 5px;
                    font-size: 11px;
                }
                .pt-sheet-status.valid {
                    background: rgba(46, 204, 113, 0.15);
                    border: 1px solid rgba(46, 204, 113, 0.4);
                    color: #2ecc71;
                }
                .pt-sheet-status.invalid {
                    background: rgba(231, 76, 60, 0.15);
                    border: 1px solid rgba(231, 76, 60, 0.4);
                    color: #e74c3c;
                }
                .pt-sheet-status.pending {
                    background: rgba(241, 196, 15, 0.15);
                    border: 1px solid rgba(241, 196, 15, 0.4);
                    color: #f1c40f;
                }
                .pt-mode-description {
                    font-size: 10px;
                    color: #666;
                    margin-top: 5px;
                    padding: 8px;
                    background: rgba(0,0,0,0.2);
                    border-radius: 4px;
                }
                /* NEW: Whitelist selector dropdown styling */
                .pt-whitelist-selector {
                    margin-bottom: 10px;
                    padding: 8px;
                    background: rgba(0,0,0,0.2);
                    border-radius: 6px;
                    border: 1px solid rgba(233, 69, 96, 0.3);
                }
                .pt-whitelist-selector label {
                    display: block;
                    margin-bottom: 5px;
                    color: #e94560;
                    font-size: 11px;
                    font-weight: bold;
                    text-transform: uppercase;
                    letter-spacing: 0.5px;
                }
                .pt-whitelist-selector select {
                    width: 100%;
                    padding: 8px 10px;
                    border: 1px solid #0f3460;
                    border-radius: 6px;
                    background: rgba(0,0,0,0.3);
                    color: #fff;
                    font-size: 13px;
                    box-sizing: border-box;
                }
                .pt-whitelist-selector select:focus {
                    outline: none;
                    border-color: #e94560;
                }
                .pt-whitelist-label {
                    font-size: 11px;
                    color: #aaa;
                    margin-bottom: 8px;
                    padding: 4px 8px;
                    background: rgba(233, 69, 96, 0.1);
                    border-radius: 4px;
                    display: inline-block;
                }
            `;

            if (typeof GM_addStyle !== "undefined") {
                GM_addStyle(css);
            } else {
                const style = document.createElement("style");
                style.textContent = css;
                document.head.appendChild(style);
            }
        },

        createToggleButton: function () {
            const btn = document.createElement("div");
            btn.id = "purchase-tracker-toggle";

            const isMobile = Device.isMobile();
            btn.innerHTML = `📦 ${isMobile ? "PT" : "Tracker"}<span class="pt-sync-indicator ${State.autoSyncEnabled ? "active" : ""}"></span>`;

            const pos = State.togglePosition;
            if (pos.x !== null) {
                btn.style.left = pos.x + "px";
                btn.style.top = pos.y + "px";
            } else {
                btn.style.right = "10px";
                btn.style.top = pos.y + "px";
            }

            this.setupDraggable(btn);
            document.body.appendChild(btn);
            this.toggleBtn = btn;
        },

        setupDraggable: function (element) {
            const self = this;

            element.addEventListener("mousedown", startDrag);
            document.addEventListener("mousemove", doDrag);
            document.addEventListener("mouseup", endDrag);

            element.addEventListener("touchstart", startDrag, { passive: false });
            document.addEventListener("touchmove", doDrag, { passive: false });
            document.addEventListener("touchend", endDrag);

            function startDrag(e) {
                self.isDragging = true;
                self.hasMoved = false;

                const clientX = e.touches ? e.touches[0].clientX : e.clientX;
                const clientY = e.touches ? e.touches[0].clientY : e.clientY;

                self.dragStartX = clientX;
                self.dragStartY = clientY;

                const rect = element.getBoundingClientRect();
                self.dragStartPosX = rect.left;
                self.dragStartPosY = rect.top;

                element.classList.add("dragging");

                if (e.touches) e.preventDefault();
            }

            function doDrag(e) {
                if (!self.isDragging) return;

                const clientX = e.touches ? e.touches[0].clientX : e.clientX;
                const clientY = e.touches ? e.touches[0].clientY : e.clientY;

                const deltaX = clientX - self.dragStartX;
                const deltaY = clientY - self.dragStartY;

                if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
                    self.hasMoved = true;
                }

                let newX = self.dragStartPosX + deltaX;
                let newY = self.dragStartPosY + deltaY;

                const maxX = window.innerWidth - element.offsetWidth - 10;
                const maxY = window.innerHeight - element.offsetHeight - 10;

                newX = Math.max(10, Math.min(newX, maxX));
                newY = Math.max(10, Math.min(newY, maxY));

                element.style.left = newX + "px";
                element.style.top = newY + "px";
                element.style.right = "auto";

                if (e.touches) e.preventDefault();
            }

            function endDrag() {
                if (!self.isDragging) return;

                self.isDragging = false;
                element.classList.remove("dragging");

                if (self.hasMoved) {
                    const rect = element.getBoundingClientRect();
                    State.togglePosition = { x: rect.left, y: rect.top };
                    State.saveTogglePosition();
                } else {
                    self.togglePanel();
                }
            }
        },

        updateToggleIndicator: function () {
            if (this.toggleBtn) {
                const indicator = this.toggleBtn.querySelector(".pt-sync-indicator");
                if (indicator) {
                    if (State.autoSyncEnabled || State.isPolling) {
                        indicator.classList.add("active");
                    } else {
                        indicator.classList.remove("active");
                    }
                }
            }
        },

        createPanel: function () {
            const panel = document.createElement("div");
            panel.id = "purchase-tracker-panel";

            const isAuthenticated = State.isAuthenticated();
            const isBothMode = State.syncMode === CONFIG.SYNC_MODES.BOTH;

            panel.innerHTML = `
                <div class="pt-header">
                    <h3>📦 Purchase Tracker</h3>
                    <button class="pt-close">&times;</button>
                </div>
                <div class="pt-body">
                    <!-- Auth Section -->
                    <div class="pt-section" id="pt-auth-section">
                        <div class="pt-section-title ${isAuthenticated ? "collapsed" : ""}" id="pt-auth-title">
                            <span>🔐 Authentication</span>
                            <span class="pt-toggle-icon">▼</span>
                        </div>
                        <div class="pt-section-content ${isAuthenticated ? "collapsed" : ""}" id="pt-auth-content">
                            <div id="pt-user-info" class="pt-user-info" style="display:${isAuthenticated ? "flex" : "none"};">
                                <span class="pt-check">✓</span>
                                <span>Authenticated as <strong id="pt-username">${State.userName || ""}</strong></span>
                            </div>
                            <div class="pt-input-group">
                                <label>Torn API Key (Full Access)</label>
                                <input type="password" id="pt-api-key" placeholder="Enter your API key" ${isAuthenticated ? "disabled" : ""}>
                            </div>
                            <div class="pt-btn-group">
                                <button class="pt-btn pt-btn-primary" id="pt-btn-auth" style="display:${isAuthenticated ? "none" : "inline-block"}">Authenticate</button>
                                <button class="pt-btn pt-btn-danger pt-btn-small" id="pt-btn-logout" style="display:${isAuthenticated ? "inline-block" : "none"}">Logout</button>
                            </div>
                        </div>
                    </div>

                    <!-- Sheet Configuration Section -->
                    <div class="pt-section" id="pt-sheets-section">
                        <div class="pt-section-title ${State.sheetsSectionCollapsed ? "collapsed" : ""}" id="pt-sheets-title">
                            <span>📊 Sheet Configuration</span>
                            <span class="pt-toggle-icon">▼</span>
                        </div>
                        <div class="pt-section-content ${State.sheetsSectionCollapsed ? "collapsed" : ""}" id="pt-sheets-content">
                            <div class="pt-input-group">
                                <label>Sync Mode</label>
                                <select id="pt-sync-mode">
                                    <option value="shared" ${State.syncMode === "shared" ? "selected" : ""}>Shared Sheet Only (Faction)</option>
                                    <option value="personal" ${State.syncMode === "personal" ? "selected" : ""}>Personal Sheet Only</option>
                                    <option value="both" ${State.syncMode === "both" ? "selected" : ""}>Both Sheets</option>
                                </select>
                                <div class="pt-mode-description" id="pt-mode-description">
                                    ${this.getSyncModeDescription(State.syncMode)}
                                </div>
                            </div>

                            <div class="pt-input-group" id="pt-shared-sheet-group" style="display:${State.syncMode !== "personal" ? "block" : "none"}">
                                <label>Shared/Faction Sheet URL</label>
                                <input type="text" id="pt-shared-sheet-url" placeholder="Paste your faction's Apps Script URL" value="${State.sharedSheetUrl}">
                                <div id="pt-shared-sheet-status" class="pt-sheet-status" style="display:none;"></div>
                            </div>

                            <div class="pt-input-group" id="pt-personal-sheet-group" style="display:${State.syncMode !== "shared" ? "block" : "none"}">
                                <label>Personal Sheet URL</label>
                                <input type="text" id="pt-personal-sheet-url" placeholder="Paste your personal Apps Script URL" value="${State.personalSheetUrl}">
                                <div id="pt-personal-sheet-status" class="pt-sheet-status" style="display:none;"></div>
                            </div>

                            <div class="pt-btn-group">
                                <button class="pt-btn pt-btn-secondary pt-btn-small" id="pt-btn-test-sheets">Test Connection(s)</button>
                                <button class="pt-btn pt-btn-primary pt-btn-small" id="pt-btn-save-sheets">Save Sheet Config</button>
                            </div>

                            <p class="pt-info">
                                💡 To create your own sheet: Copy the master template, deploy your own Apps Script, and paste the URL above.
                            </p>
                        </div>
                    </div>

                    <!-- Whitelist Section -->
                    <div class="pt-section" id="pt-whitelist-section">
                        <div class="pt-section-title" id="pt-whitelist-title">
                            <span>📋 Item Whitelist</span>
                            <span class="pt-toggle-icon">▼</span>
                        </div>
                        <div class="pt-section-content" id="pt-whitelist-content">
                            <!-- NEW: Whitelist selector dropdown - only visible in "both" mode -->
                            <div class="pt-whitelist-selector" id="pt-whitelist-selector" style="display:${isBothMode ? "block" : "none"};">
                                <label>Editing Whitelist For:</label>
                                <select id="pt-whitelist-which">
                                    <option value="shared" ${State.lastViewedWhitelist === "shared" ? "selected" : ""}>🏰 Shared/Faction Sheet</option>
                                    <option value="personal" ${State.lastViewedWhitelist === "personal" ? "selected" : ""}>📝 Personal Sheet</option>
                                </select>
                            </div>

                            <!-- Label showing which whitelist is being edited (shown in all modes) -->
                            <div class="pt-whitelist-label" id="pt-whitelist-active-label">
                                ${this.getWhitelistLabel()}
                            </div>

                            <p class="pt-info" id="pt-whitelist-info">${this.getWhitelistInfoText()}</p>

                            <div class="pt-input-group">
                                <label>Add Item (ID or Name)</label>
                                <input type="text" id="pt-whitelist-input" placeholder="e.g., 67 or First Aid Kit">
                                <div class="pt-search-results" id="pt-search-results"></div>
                            </div>
                            <div class="pt-btn-group">
                                <button class="pt-btn pt-btn-secondary pt-btn-small" id="pt-btn-add-whitelist">Add Item</button>
                                <button class="pt-btn pt-btn-secondary pt-btn-small" id="pt-btn-clear-whitelist">Clear This List</button>
                            </div>
                            <div class="pt-whitelist" id="pt-whitelist-container">
                                <em style="color:#666;">No items in whitelist (tracking all)</em>
                            </div>
                        </div>
                    </div>

                    <!-- Sync Section -->
                    <div class="pt-section" id="pt-sync-section">
                        <div class="pt-section-title">🔄 Sync Controls</div>
                        <div class="pt-btn-group">
                            <button class="pt-btn pt-btn-primary" id="pt-btn-sync-now">Sync Now</button>
                            <button class="pt-btn pt-btn-success" id="pt-btn-toggle-polling">Start Auto-Sync</button>
                        </div>
                        <div class="pt-btn-group" style="margin-top: 5px;">
                            <button class="pt-btn pt-btn-secondary pt-btn-small" id="pt-btn-sync-display">Sync Display Case</button>
                        </div>
                        <p class="pt-info">Auto-sync runs every minute and persists across page changes.</p>
                        <div class="pt-status" id="pt-status">Ready</div>
                    </div>

                    <!-- Settings Section -->
                    <div class="pt-section">
                        <div class="pt-section-title">⚙️ Settings</div>
                        <div class="pt-btn-group">
                            <button class="pt-btn pt-btn-secondary pt-btn-small" id="pt-btn-refresh-items">Refresh Item Cache</button>
                            <button class="pt-btn pt-btn-secondary pt-btn-small" id="pt-btn-reset-position">Reset Button Position</button>
                            <button class="pt-btn pt-btn-danger pt-btn-small" id="pt-btn-reset">Reset All Data</button>
                        </div>
                        <p class="pt-info">
                            Version: ${CONFIG.VERSION} | Item cache: ${Object.keys(State.itemCache).length} items<br>
                            Active sheets: ${State.getActiveSheetUrls().length}
                        </p>
                    </div>
                </div>
            `;

            document.body.appendChild(panel);
            this.panel = panel;
            this.statusEl = panel.querySelector("#pt-status");

            this.bindEvents();
            this.updateWhitelistUI();
            this.updatePollingButton();
        },

        // NEW: Get a human-readable label for the currently active whitelist
        getWhitelistLabel: function () {
            const type = State.getCurrentWhitelistType();
            if (State.syncMode === CONFIG.SYNC_MODES.BOTH) {
                return type === "personal"
                    ? "📝 Editing: Personal Sheet Whitelist"
                    : "🏰 Editing: Shared/Faction Sheet Whitelist";
            }
            if (State.syncMode === CONFIG.SYNC_MODES.PERSONAL_ONLY) {
                return "📝 Personal Sheet Whitelist";
            }
            return "🏰 Shared/Faction Sheet Whitelist";
        },

        // NEW: Get info text for the whitelist section based on mode
        getWhitelistInfoText: function () {
            if (State.syncMode === CONFIG.SYNC_MODES.BOTH) {
                return "Each sheet has its own whitelist. Use the dropdown above to switch between them. Only purchases of whitelisted items will be sent to each respective sheet. Leave a list empty to track all items for that sheet.";
            }
            return "Only purchases of these items will be tracked. Leave empty to track all.";
        },

        getSyncModeDescription: function (mode) {
            switch (mode) {
                case "personal":
                    return "📝 Logs will only be saved to your personal sheet.";
                case "shared":
                    return "👥 Logs will only be saved to the shared faction sheet.";
                case "both":
                    return "📝👥 Logs will be saved to BOTH your personal sheet AND the shared faction sheet. Each sheet can have its own item whitelist.";
                default:
                    return "";
            }
        },

        bindEvents: function () {
            const panel = this.panel;

            panel.querySelector(".pt-close").addEventListener("click", () => this.togglePanel());
            panel.querySelector("#pt-auth-title").addEventListener("click", () => this.toggleSection("auth"));
            panel.querySelector("#pt-sheets-title").addEventListener("click", () => this.toggleSection("sheets"));
            panel.querySelector("#pt-whitelist-title").addEventListener("click", () => this.toggleSection("whitelist"));

            panel.querySelector("#pt-btn-auth").addEventListener("click", () => this.handleAuth());
            panel.querySelector("#pt-btn-logout").addEventListener("click", () => this.handleLogout());

            // Sheet configuration events
            panel.querySelector("#pt-sync-mode").addEventListener("change", (e) => this.handleSyncModeChange(e.target.value));
            panel.querySelector("#pt-btn-test-sheets").addEventListener("click", () => this.handleTestSheets());
            panel.querySelector("#pt-btn-save-sheets").addEventListener("click", () => this.handleSaveSheets());

            // NEW: Whitelist selector change event
            panel.querySelector("#pt-whitelist-which").addEventListener("change", (e) => {
                State.lastViewedWhitelist = e.target.value;
                Storage.set("lastViewedWhitelist", State.lastViewedWhitelist);
                this.updateWhitelistUI();
            });

            // Whitelist events
            const whitelistInput = panel.querySelector("#pt-whitelist-input");
            whitelistInput.addEventListener("input", (e) => this.handleWhitelistSearch(e.target.value));
            whitelistInput.addEventListener("keypress", (e) => {
                if (e.key === "Enter") this.handleAddWhitelist();
            });
            whitelistInput.addEventListener("focus", (e) => {
                if (e.target.value.length >= 2) {
                    this.handleWhitelistSearch(e.target.value);
                }
            });

            document.addEventListener("click", (e) => {
                if (!e.target.closest("#pt-whitelist-input") && !e.target.closest("#pt-search-results")) {
                    panel.querySelector("#pt-search-results").classList.remove("visible");
                }
            });

            panel.querySelector("#pt-btn-add-whitelist").addEventListener("click", () => this.handleAddWhitelist());
            panel.querySelector("#pt-btn-clear-whitelist").addEventListener("click", () => this.handleClearWhitelist());
            panel.querySelector("#pt-btn-sync-now").addEventListener("click", () => Sync.run());
            panel.querySelector("#pt-btn-toggle-polling").addEventListener("click", () => this.handleTogglePolling());
            panel.querySelector("#pt-btn-sync-display").addEventListener("click", () => Sync.syncDisplayCase());
            panel.querySelector("#pt-btn-refresh-items").addEventListener("click", () => ItemCache.refresh());
            panel.querySelector("#pt-btn-reset-position").addEventListener("click", () => this.handleResetPosition());
            panel.querySelector("#pt-btn-reset").addEventListener("click", () => this.handleReset());
        },

        togglePanel: function () {
            this.panel.classList.toggle("visible");
        },

        toggleSection: function (section) {
            const titleEl = this.panel.querySelector(`#pt-${section}-title`);
            const contentEl = this.panel.querySelector(`#pt-${section}-content`);
            titleEl.classList.toggle("collapsed");
            contentEl.classList.toggle("collapsed");

            if (section === "sheets") {
                State.sheetsSectionCollapsed = titleEl.classList.contains("collapsed");
                State.saveSheetsSectionState();
            }
        },

        setStatus: function (message, type = "") {
            if (this.statusEl) {
                this.statusEl.textContent = message;
                this.statusEl.className = "pt-status " + type;
            }
        },

        handleSyncModeChange: function (mode) {
            State.syncMode = mode;

            // Update visibility of URL inputs
            const sharedGroup = this.panel.querySelector("#pt-shared-sheet-group");
            const personalGroup = this.panel.querySelector("#pt-personal-sheet-group");
            const descriptionEl = this.panel.querySelector("#pt-mode-description");

            sharedGroup.style.display = mode !== "personal" ? "block" : "none";
            personalGroup.style.display = mode !== "shared" ? "block" : "none";
            descriptionEl.innerHTML = this.getSyncModeDescription(mode);

            // NEW: Show/hide whitelist selector based on mode
            const whitelistSelector = this.panel.querySelector("#pt-whitelist-selector");
            whitelistSelector.style.display = mode === "both" ? "block" : "none";

            // When switching to a single-sheet mode, auto-set the lastViewedWhitelist
            // so getCurrentWhitelist returns the correct one
            if (mode === "personal") {
                State.lastViewedWhitelist = "personal";
            } else if (mode === "shared") {
                State.lastViewedWhitelist = "shared";
            }
            // In "both" mode, keep whatever was last selected
            Storage.set("lastViewedWhitelist", State.lastViewedWhitelist);

            // Refresh the whitelist UI to show the correct list
            this.updateWhitelistUI();
        },

        handleTestSheets: async function () {
            this.setStatus("Testing sheet connection(s)...");

            const mode = this.panel.querySelector("#pt-sync-mode").value;
            const sharedUrl = this.panel.querySelector("#pt-shared-sheet-url").value.trim();
            const personalUrl = this.panel.querySelector("#pt-personal-sheet-url").value.trim();

            let testsRun = 0;
            let testsPassed = 0;

            if (mode !== "personal" && sharedUrl) {
                testsRun++;
                const statusEl = this.panel.querySelector("#pt-shared-sheet-status");
                statusEl.style.display = "flex";
                statusEl.className = "pt-sheet-status pending";
                statusEl.textContent = "Testing...";

                const result = await API.testSheetUrl(sharedUrl);
                if (result.valid) {
                    statusEl.className = "pt-sheet-status valid";
                    statusEl.textContent = `✓ Connected: ${result.data.sheetName || "Sheet OK"}`;
                    testsPassed++;
                } else {
                    statusEl.className = "pt-sheet-status invalid";
                    statusEl.textContent = `✗ Error: ${result.error}`;
                }
            }

            if (mode !== "shared" && personalUrl) {
                testsRun++;
                const statusEl = this.panel.querySelector("#pt-personal-sheet-status");
                statusEl.style.display = "flex";
                statusEl.className = "pt-sheet-status pending";
                statusEl.textContent = "Testing...";

                const result = await API.testSheetUrl(personalUrl);
                if (result.valid) {
                    statusEl.className = "pt-sheet-status valid";
                    statusEl.textContent = `✓ Connected: ${result.data.sheetName || "Sheet OK"}`;
                    testsPassed++;
                } else {
                    statusEl.className = "pt-sheet-status invalid";
                    statusEl.textContent = `✗ Error: ${result.error}`;
                }
            }

            if (testsRun === 0) {
                this.setStatus("No sheet URLs to test", "error");
            } else if (testsPassed === testsRun) {
                this.setStatus(`All ${testsRun} sheet(s) connected successfully!`, "success");
            } else {
                this.setStatus(`${testsPassed}/${testsRun} sheets connected`, "error");
            }
        },

        handleSaveSheets: function () {
            const mode = this.panel.querySelector("#pt-sync-mode").value;
            const sharedUrl = this.panel.querySelector("#pt-shared-sheet-url").value.trim();
            const personalUrl = this.panel.querySelector("#pt-personal-sheet-url").value.trim();

            if (mode === "shared" && !sharedUrl) {
                this.setStatus("Please enter a shared sheet URL", "error");
                return;
            }
            if (mode === "personal" && !personalUrl) {
                this.setStatus("Please enter a personal sheet URL", "error");
                return;
            }
            if (mode === "both" && (!sharedUrl || !personalUrl)) {
                this.setStatus("Please enter both sheet URLs", "error");
                return;
            }

            State.syncMode = mode;
            State.sharedSheetUrl = sharedUrl;
            State.personalSheetUrl = personalUrl;
            State.saveSheetConfig();

            this.setStatus("Sheet configuration saved!", "success");

            // Refresh whitelist UI to reflect mode change
            this.updateWhitelistUI();

            setTimeout(() => {
                const sheetsTitle = this.panel.querySelector("#pt-sheets-title");
                const sheetsContent = this.panel.querySelector("#pt-sheets-content");
                sheetsTitle.classList.add("collapsed");
                sheetsContent.classList.add("collapsed");

                State.sheetsSectionCollapsed = true;
                State.saveSheetsSectionState();
            }, 1500);
        },

        updateAuthUI: function (showSuccess = false) {
            const apiKeyInput = this.panel.querySelector("#pt-api-key");
            const authBtn = this.panel.querySelector("#pt-btn-auth");
            const logoutBtn = this.panel.querySelector("#pt-btn-logout");
            const userInfo = this.panel.querySelector("#pt-user-info");
            const usernameEl = this.panel.querySelector("#pt-username");
            const authTitle = this.panel.querySelector("#pt-auth-title");
            const authContent = this.panel.querySelector("#pt-auth-content");

            if (State.isAuthenticated()) {
                apiKeyInput.value = "••••••••••••••••";
                apiKeyInput.disabled = true;
                authBtn.style.display = "none";
                logoutBtn.style.display = "inline-block";
                userInfo.style.display = "flex";
                usernameEl.textContent = State.userName;

                if (showSuccess) {
                    setTimeout(() => {
                        authTitle.classList.add("collapsed");
                        authContent.classList.add("collapsed");
                    }, 1500);
                }
            } else {
                apiKeyInput.value = State.apiKey || "";
                apiKeyInput.disabled = false;
                authBtn.style.display = "inline-block";
                logoutBtn.style.display = "none";
                userInfo.style.display = "none";
                authTitle.classList.remove("collapsed");
                authContent.classList.remove("collapsed");
            }
        },

        // REWRITTEN: Update whitelist UI to show the correct list based on mode and selection
        updateWhitelistUI: function () {
            const container = this.panel.querySelector("#pt-whitelist-container");
            const selectorEl = this.panel.querySelector("#pt-whitelist-selector");
            const labelEl = this.panel.querySelector("#pt-whitelist-active-label");
            const infoEl = this.panel.querySelector("#pt-whitelist-info");
            const whichSelect = this.panel.querySelector("#pt-whitelist-which");

            const isBothMode = State.syncMode === CONFIG.SYNC_MODES.BOTH;

            // Show/hide the dropdown selector
            selectorEl.style.display = isBothMode ? "block" : "none";

            // Update the dropdown to reflect current selection
            if (whichSelect) {
                whichSelect.value = State.lastViewedWhitelist;
            }

            // Update labels and info
            labelEl.innerHTML = this.getWhitelistLabel();
            infoEl.textContent = this.getWhitelistInfoText();

            // Get the currently active whitelist
            const currentWhitelist = State.getCurrentWhitelist();

            if (currentWhitelist.length === 0) {
                container.innerHTML = '<em style="color:#666;">No items in whitelist (tracking all)</em>';
                return;
            }

            container.innerHTML = currentWhitelist
                .map((itemId) => {
                    const itemName = ItemCache.getItemName(itemId);
                    return `
                        <div class="pt-whitelist-item">
                            <span>${itemName} (#${itemId})</span>
                            <button data-item-id="${itemId}">✕</button>
                        </div>
                    `;
                })
                .join("");

            container.querySelectorAll("button").forEach((btn) => {
                btn.addEventListener("click", () => {
                    const itemId = parseInt(btn.getAttribute("data-item-id"));
                    // Remove from the currently active whitelist
                    const wl = State.getCurrentWhitelist();
                    const idx = wl.indexOf(itemId);
                    if (idx > -1) {
                        wl.splice(idx, 1);
                        State.save();
                        this.updateWhitelistUI();
                        this.setStatus(`Removed item #${itemId} from ${State.getCurrentWhitelistType()} whitelist`);
                    }
                });
            });
        },

        updatePollingButton: function () {
            const btn = this.panel.querySelector("#pt-btn-toggle-polling");
            if (State.isPolling || State.autoSyncEnabled) {
                btn.textContent = "Stop Auto-Sync";
                btn.classList.remove("pt-btn-success");
                btn.classList.add("pt-btn-danger");
            } else {
                btn.textContent = "Start Auto-Sync";
                btn.classList.remove("pt-btn-danger");
                btn.classList.add("pt-btn-success");
            }
        },

        handleAuth: async function () {
            const apiKey = this.panel.querySelector("#pt-api-key").value.trim();

            if (!apiKey) {
                this.setStatus("Please enter your API key", "error");
                return;
            }

            this.setStatus("Validating API key...");

            const apiResult = await Auth.validateApiKey(apiKey);
            if (!apiResult.success) {
                this.setStatus("Invalid API key: " + apiResult.error, "error");
                return;
            }

            this.setStatus(`✓ Authenticated as ${State.userName}!`, "success");
            this.updateAuthUI(true);

            if (ItemCache.needsRefresh()) {
                await ItemCache.refresh();
            }
        },

        handleLogout: function () {
            Sync.stopPolling();
            State.apiKey = "";
            State.userId = null;
            State.userName = "";
            State.save();
            this.updateAuthUI();
            this.setStatus("Logged out");
        },

        handleWhitelistSearch: function (value) {
            const resultsContainer = this.panel.querySelector("#pt-search-results");

            if (!value || value.length < 2) {
                resultsContainer.classList.remove("visible");
                return;
            }

            if (/^\d+$/.test(value)) {
                resultsContainer.classList.remove("visible");
                return;
            }

            if (Object.keys(State.itemCache).length === 0) {
                resultsContainer.innerHTML = '<div class="pt-search-result" style="color:#e94560;">Item cache empty. Click "Refresh Item Cache" first.</div>';
                resultsContainer.classList.add("visible");
                return;
            }

            const results = ItemCache.searchByName(value);

            if (results.length === 0) {
                resultsContainer.innerHTML = '<div class="pt-search-result" style="color:#888;">No items found</div>';
                resultsContainer.classList.add("visible");
                return;
            }

            const displayResults = results.slice(0, 10);

            resultsContainer.innerHTML = displayResults
                .map(
                    (item) => `
                    <div class="pt-search-result" data-item-id="${item.id}">
                        <strong>${item.name}</strong> (#${item.id})<br>
                        <span class="pt-item-type">${item.type}</span>
                    </div>
                `,
                )
                .join("");

            resultsContainer.querySelectorAll(".pt-search-result").forEach((el) => {
                el.addEventListener("click", () => {
                    const itemId = parseInt(el.getAttribute("data-item-id"));
                    const currentWhitelist = State.getCurrentWhitelist();
                    const whitelistType = State.getCurrentWhitelistType();

                    if (itemId && !currentWhitelist.includes(itemId)) {
                        currentWhitelist.push(itemId);
                        State.save();
                        this.updateWhitelistUI();
                        this.setStatus(`Added ${ItemCache.getItemName(itemId)} to ${whitelistType} whitelist`, "success");
                    } else if (itemId) {
                        this.setStatus(`${ItemCache.getItemName(itemId)} already in ${whitelistType} whitelist`);
                    }
                    this.panel.querySelector("#pt-whitelist-input").value = "";
                    resultsContainer.classList.remove("visible");
                });
            });

            resultsContainer.classList.add("visible");
        },

        handleAddWhitelist: async function () {
            const input = this.panel.querySelector("#pt-whitelist-input");
            const value = input.value.trim();

            if (!value) return;

            const currentWhitelist = State.getCurrentWhitelist();
            const whitelistType = State.getCurrentWhitelistType();

            if (/^\d+$/.test(value)) {
                const itemId = parseInt(value);
                if (!currentWhitelist.includes(itemId)) {
                    currentWhitelist.push(itemId);
                    State.save();
                    this.updateWhitelistUI();
                    this.setStatus(`Added ${ItemCache.getItemName(itemId)} (#${itemId}) to ${whitelistType} whitelist`, "success");
                } else {
                    this.setStatus(`Item #${itemId} already in ${whitelistType} whitelist`);
                }
                input.value = "";
                this.panel.querySelector("#pt-search-results").classList.remove("visible");
                return;
            }

            if (Object.keys(State.itemCache).length === 0) {
                this.setStatus('Item cache empty. Click "Refresh Item Cache" first.', "error");
                return;
            }

            const results = ItemCache.searchByName(value);

            if (results.length === 0) {
                this.setStatus('No items found matching "' + value + '"', "error");
                return;
            }

            const item = results[0];
            if (!currentWhitelist.includes(item.id)) {
                currentWhitelist.push(item.id);
                State.save();
                this.updateWhitelistUI();
                this.setStatus(`Added ${item.name} (#${item.id}) to ${whitelistType} whitelist`, "success");
            } else {
                this.setStatus(`${item.name} already in ${whitelistType} whitelist`);
            }

            input.value = "";
            this.panel.querySelector("#pt-search-results").classList.remove("visible");
        },

        // UPDATED: Only clears the currently active whitelist
        handleClearWhitelist: function () {
            const whitelistType = State.getCurrentWhitelistType();
            const label = whitelistType === "personal" ? "Personal" : "Shared/Faction";

            if (confirm(`Clear all items from the ${label} whitelist? This will track ALL purchases for that sheet.`)) {
                if (whitelistType === "personal") {
                    State.personalWhitelist = [];
                } else {
                    State.sharedWhitelist = [];
                }
                State.save();
                this.updateWhitelistUI();
                this.setStatus(`${label} whitelist cleared`);
            }
        },

        handleTogglePolling: function () {
            if (State.isPolling || State.autoSyncEnabled) {
                Sync.stopPolling();
                this.setStatus("Auto-sync stopped");
            } else {
                if (!State.isAuthenticated()) {
                    this.setStatus("Please authenticate first", "error");
                    return;
                }
                if (!State.hasValidSheetConfig()) {
                    this.setStatus("Please configure sheet URL(s) first", "error");
                    return;
                }
                Sync.startPolling();
                this.setStatus("Auto-sync started (persists across pages)");
                Sync.run();
            }
        },

        handleResetPosition: function () {
            State.togglePosition = { x: null, y: 100 };
            State.saveTogglePosition();

            if (this.toggleBtn) {
                this.toggleBtn.style.left = "auto";
                this.toggleBtn.style.right = "10px";
                this.toggleBtn.style.top = "100px";
            }

            this.setStatus("Button position reset", "success");
        },

        // UPDATED: Clean up all new storage keys on reset
        handleReset: function () {
            if (confirm("This will delete ALL local data including API key, sheet URLs, whitelists, and caches. Continue?")) {
                Sync.stopPolling();
                Storage.delete("apiKey");
                Storage.delete("userId");
                Storage.delete("userName");
                Storage.delete("whitelist"); // old key, just in case
                Storage.delete("personalWhitelist");
                Storage.delete("sharedWhitelist");
                Storage.delete("lastViewedWhitelist");
                Storage.delete("lastSyncTimestamp");
                Storage.delete("processedLogIds");
                Storage.delete("itemCache");
                Storage.delete("itemCacheTimestamp");
                Storage.delete("sellerCache");
                Storage.delete("autoSyncEnabled");
                Storage.delete("togglePosition");
                Storage.delete("personalSheetUrl");
                Storage.delete("sharedSheetUrl");
                Storage.delete("syncMode");
                Storage.delete("sheetsSectionCollapsed");
                location.reload();
            }
        },
    };

    // ============================================
    // INITIALIZATION
    // ============================================
    function init() {
        console.log("[PurchaseTracker] Initializing v" + CONFIG.VERSION);
        UI.init();

        setTimeout(() => {
            Sync.resumePollingIfEnabled();
        }, 1000);
    }

    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", init);
    } else {
        init();
    }
})();