Purchase Tracker

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

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 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();
    }
})();