import { ShopEditorRegistry } from "../services/ShopEditorRegistry.js";
import { ShopDossier } from "./ShopDossier.js";
import { ShopProductProviderRegistry } from "../services/ShopProductProviderRegistry.js";
import { ShopQuoteService } from "../services/ShopQuoteService.js";
import { ShopStore } from "../services/ShopStore.js";
import { ShopSystemAdapterRegistry } from "../services/ShopSystemAdapterRegistry.js";
import { ShopTransactionService } from "../services/ShopTransactionService.js";
import { ShopRestockService } from "../services/ShopRestockService.js";
import { ShopModel } from "../models/ShopModel.js";

const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;

export class ShopApplication extends HandlebarsApplicationMixin(ApplicationV2) {
    #shopId = "";
    #shop = null;
    #products = new Map();
    #tooltips = new Map();
    #adapter = null;
    #wallet = null;
    #inventoryActor = null;
    #purchaseCart = new Map();
    #saleCart = new Map();
    #busy = false;
    #inventoryHook = null;
    #shopHook = null;

    static DEFAULT_OPTIONS = {
        id: "augur-nexus-shop",
        classes: ["augur-nexus", "nexus-shop-screen-window"],
        tag: "div",
        window: { title: "Shop", resizable: true, minimizable: true },
        position: { width: 1160, height: 800 },
        actions: {
            closeShop: ShopApplication._onClose,
            openShopJournal: ShopApplication._onOpenJournal,
            configureShop: ShopApplication._onConfigureShop,
            restockShop: ShopApplication._onRestock,
            transferPurchase: ShopApplication._onTransferPurchase,
            returnPurchase: ShopApplication._onReturnPurchase,
            transferSale: ShopApplication._onTransferSale,
            returnSale: ShopApplication._onReturnSale,
            selectShopWallet: ShopApplication._onSelectWallet,
            selectShopInventory: ShopApplication._onSelectInventory,
            useAssignedCharacter: ShopApplication._onUseAssignedCharacter,
            clearTrade: ShopApplication._onClearTrade,
            completeTrade: ShopApplication._onCompleteTrade
        }
    };

    static PARTS = {
        content: { template: "modules/augur-nexus/templates/features/shops/shop.hbs" }
    };

    static show({ shopId = "" } = {}) {
        if (!shopId) return null;
        const app = new this({ shopId });
        app.render(true, { focus: true });
        return app;
    }

    constructor({ shopId = "" } = {}, options = {}) {
        super(options);
        this.#shopId = shopId;
        this.#inventoryHook = Hooks.on("augurNexusShopInventoryChanged", ({ shopId: changedId } = {}) => {
            if (this.#matchesShop(changedId) && this.rendered && !this.#busy) this.render({ parts: ["content"] });
        });
        this.#shopHook = Hooks.on("augurNexusShopsChanged", ({ shop } = {}) => {
            if (this.#matchesShop(shop?.id) && this.rendered && !this.#busy) this.render({ parts: ["content"] });
        });
    }

    get title() { return this.#shop?.name || "Shop"; }

    async _prepareContext() {
        this.#shop = ShopStore.getShop(this.#shopId);
        if (!this.#shop) return { missing: true };
        this.#adapter = ShopSystemAdapterRegistry.getActive();
        if (this.#adapter && !this.#wallet) this.#wallet = await this.#adapter.getDefaultWallet();
        if (this.#adapter && this.#wallet && !this.#inventoryActor) {
            this.#inventoryActor = await this.#adapter.getDefaultInventoryHolder(this.#wallet);
        }
        await this.#loadProducts();
        const rawActorItems = await this.#adapter?.getInventoryItems(this.#inventoryActor, { shop: this.#shop }) || [];
        const actorItems = await Promise.all(rawActorItems.map(async entry => {
            const item = this.#inventoryActor?.items?.get(entry.itemId);
            const quote = await ShopQuoteService.quoteCustomerSale({
                shop: this.#shop,
                item,
                actor: this.#inventoryActor,
                adapterQuote: { eligible: entry.eligible, reason: entry.reason, baseAmount: entry.baseAmount }
            });
            return { ...entry, eligible: quote.allowed, reason: quote.reason, saleAmount: quote.amount, priceClass: quote.priceClass };
        }));
        this.#reconcileCarts(actorItems);
        this.#tooltips.clear();

        const purchaseEntries = await this.#preparePurchaseEntries();
        const saleEntries = this.#prepareSaleEntries(actorItems);
        const playerCards = [
            ...saleEntries.filter(entry => entry.remaining > 0).map(entry => this.#realSaleCard(entry)),
            ...purchaseEntries.filter(entry => entry.selected > 0).map(entry => this.#incomingPurchaseCard(entry))
        ];
        const shopCards = [
            ...purchaseEntries.filter(entry => entry.infinite || entry.remaining > 0).map(entry => this.#realPurchaseCard(entry)),
            ...saleEntries.filter(entry => entry.selected > 0).map(entry => this.#incomingSaleCard(entry))
        ];

        const purchaseTotal = purchaseEntries.reduce((sum, entry) => sum + entry.priceEach * entry.selected, 0);
        const saleTotal = saleEntries.reduce((sum, entry) => sum + entry.saleAmount * entry.selected, 0);
        const netAmount = purchaseTotal - saleTotal;
        const walletView = await this.#adapter?.getActorView(this.#wallet) || null;
        const inventoryView = this.#inventoryActor?.uuid === this.#wallet?.uuid
            ? walletView
            : await this.#adapter?.getActorView(this.#inventoryActor) || null;
        const wallets = this.#adapter?.getEligibleWallets(game.user) || [];
        const inventories = this.#adapter?.getEligibleInventoryHolders(game.user) || [];
        const sameActor = Boolean(walletView && inventoryView && walletView.uuid === inventoryView.uuid);

        const activeGM = game.user.isGM || Boolean(game.users?.activeGM || game.users?.find(user => user.active && user.isGM));
        const hasLines = this.#purchaseCount() + this.#saleCount() > 0;
        let affordable = true;
        if (netAmount > 0 && this.#wallet) {
            try { affordable = this.#adapter.canAfford(this.#wallet, netAmount); }
            catch (_error) { affordable = false; }
        }
        const shopCanPay = netAmount >= 0 || this.#shop.treasury.unlimited || this.#shop.treasury.amount >= Math.abs(netAmount);
        const canComplete = Boolean(this.#adapter && this.#wallet && this.#inventoryActor && hasLines
            && affordable && shopCanPay && activeGM && !this.#busy);
        let completionReason = "Click an item to move it across the counter. Shift-click moves the whole stack.";
        if (!this.#adapter) completionReason = `${game.system.title} does not have a Shop adapter yet.`;
        else if (!this.#wallet) completionReason = "Choose an eligible wallet.";
        else if (!this.#inventoryActor) completionReason = "Choose a trading inventory.";
        else if (!activeGM) completionReason = "An active GM connection is required to update this Shop.";
        else if (!affordable) completionReason = `${this.#wallet.name} cannot cover the net payment.`;
        else if (!shopCanPay) completionReason = `The Shop cannot cover the ${this.#formatPrice(Math.abs(netAmount))} payout.`;
        else if (hasLines && netAmount > 0) completionReason = `${this.#wallet.name} will pay ${this.#formatPrice(netAmount)}.`;
        else if (hasLines && netAmount < 0) completionReason = `${this.#wallet.name} will receive ${this.#formatPrice(Math.abs(netAmount))}.`;
        else if (hasLines) completionReason = "The selected goods exchange evenly; no money changes hands.";

        const providerIds = [...new Set(this.#shop.inventory.map(row => row.providerId))];
        const canRestock = game.user.isGM && (this.#shop.restockProfile?.entries?.some(entry => entry.enabled !== false) || providerIds.some(id =>
            typeof ShopProductProviderRegistry.get(id)?.restock === "function"
        ));
        return {
            missing: false,
            isGM: game.user.isGM,
            busy: this.#busy,
            canRestock,
            shopName: this.#shop.name,
            shopImageSrc: ShopModel.getImage(this.#shop),
            parentLabel: ShopStore.getParentLabel(this.#shop),
            adapterLabel: this.#adapter?.label || "Unsupported system",
            themeId: this.#shop.configuration.themeId,
            wallet: walletView,
            inventoryActor: inventoryView,
            sameActor,
            separateWallet: Boolean(walletView && !sameActor),
            walletGroups: this.#adapter?.groupActors(wallets, this.#wallet?.uuid) || [],
            inventoryGroups: this.#adapter?.groupActors(inventories, this.#inventoryActor?.uuid) || [],
            hasWallets: wallets.length > 0,
            hasInventories: inventories.length > 0,
            assignedCharacterAvailable: this.#adapter?.isEligibleWallet(game.user.character, game.user) || false,
            playerCards,
            shopCards,
            inventoryCount: actorItems.reduce((sum, item) => sum + item.quantity, 0),
            stockCount: this.#shop.configuration.finiteStock
                ? this.#shop.inventory.reduce((sum, row) => sum + row.quantity, 0)
                : "\u221e",
            purchaseCount: this.#purchaseCount(),
            saleCount: this.#saleCount(),
            purchaseTotalLabel: this.#formatPrice(purchaseTotal),
            saleTotalLabel: this.#formatPrice(saleTotal),
            netLabel: this.#formatPrice(Math.abs(netAmount)),
            netPays: netAmount > 0,
            netReceives: netAmount < 0,
            netEven: netAmount === 0,
            treasuryLabel: this.#shop.treasury.unlimited ? "Unlimited" : this.#formatPrice(this.#shop.treasury.amount),
            buyRateLabel: `${Math.round(this.#shop.configuration.customerSaleMultiplier * 100)}%`,
            usesTradePolicy: Boolean(this.#shop.configuration.tradePolicyId),
            sellRateLabel: `${Math.round(this.#shop.configuration.customerPurchaseMultiplier * 100)}%`,
            canComplete,
            completionReason,
            hasLines,
            pricingBasis: this.#shop.configuration.pricingBasis
        };
    }

    _onRender(context, options) {
        super._onRender(context, options);
        if (this.window?.title) this.window.title.textContent = this.title;
        this.#activateDropZone("[data-wallet-drop]", "wallet");
        this.#activateDropZone("[data-inventory-drop]", "inventory");
        this.#activateTooltips();
    }

    async _onClose(options) {
        if (this.#inventoryHook !== null) Hooks.off("augurNexusShopInventoryChanged", this.#inventoryHook);
        if (this.#shopHook !== null) Hooks.off("augurNexusShopsChanged", this.#shopHook);
        return super._onClose(options);
    }

    static async _onClose() { return this.close(); }

    static async _onOpenJournal() {
        ShopDossier.show({ shopId: this.#shop?.id || this.#shopId });
    }

    static async _onConfigureShop() {
        if (game.user.isGM && this.#shop) ShopEditorRegistry.open({ shopId: this.#shop.id });
    }

    static async _onRestock() {
        if (!game.user.isGM || this.#busy) return;
        if (this.#shop.restockProfile?.entries?.length) {
            const result = await ShopRestockService.generate(this.#shop.restockProfile, {
                shop: this.#shop,
                seed: foundry.utils.randomID(),
                warn: true
            });
            const pinned = this.#shop.inventory.filter(row => row.pinned || !row.generated);
            await ShopStore.updateShop(this.#shop.id, {
                inventory: [...pinned, ...result.inventory],
                revision: this.#shop.revision + 1
            });
        } else {
            for (const id of [...new Set(this.#shop.inventory.map(row => row.providerId))]) {
                const provider = ShopProductProviderRegistry.get(id);
                if (typeof provider?.restock !== "function") continue;
                const inventory = await provider.restock(this.#shop);
                if (Array.isArray(inventory)) await ShopStore.updateShop(this.#shop.id, { inventory, revision: this.#shop.revision + 1 });
                break;
            }
        }
        this.#purchaseCart.clear();
        this.render({ parts: ["content"] });
    }
    static async _onTransferPurchase(event, target) { this.#changePurchase(target, event.shiftKey ? "all" : 1); }
    static async _onReturnPurchase(event, target) { this.#changePurchase(target, event.shiftKey ? "none" : -1); }
    static async _onTransferSale(event, target) { this.#changeSale(target, event.shiftKey ? "all" : 1); }
    static async _onReturnSale(event, target) { this.#changeSale(target, event.shiftKey ? "none" : -1); }

    static async _onSelectWallet(_event, target) {
        const actor = await this.#adapter?.resolveActor(target?.dataset?.actorUuid || "");
        if (!actor) return;
        target.closest("details")?.removeAttribute("open");
        await this.#setWallet(actor);
    }

    static async _onSelectInventory(_event, target) {
        const actor = await this.#adapter?.resolveActor(target?.dataset?.actorUuid || "");
        if (!actor) return;
        target.closest("details")?.removeAttribute("open");
        await this.#setInventoryActor(actor);
    }

    static async _onUseAssignedCharacter() {
        if (this.#adapter?.isEligibleWallet(game.user.character, game.user)) await this.#setWallet(game.user.character);
    }

    static async _onClearTrade() {
        if (this.#busy) return;
        this.#purchaseCart.clear();
        this.#saleCart.clear();
        this.render({ parts: ["content"] });
    }

    static async _onCompleteTrade() {
        if (this.#busy || !this.#wallet || !this.#inventoryActor || (!this.#purchaseCount() && !this.#saleCount())) return;
        this.#busy = true;
        await this.render({ parts: ["content"] });
        try {
            const result = await ShopTransactionService.requestTrade({
                shopId: this.#shop.id,
                walletUuid: this.#wallet.uuid,
                inventoryUuid: this.#inventoryActor.uuid,
                purchaseLines: Array.from(this.#purchaseCart, ([key, quantity]) => {
                    const product = this.#products.get(key);
                    return { providerId: product.providerId, productId: product.productId, quantity };
                }),
                saleLines: Array.from(this.#saleCart, ([itemId, quantity]) => ({ itemId, quantity }))
            });
            this.#purchaseCart.clear();
            this.#saleCart.clear();
            const settlement = result.netAmount > 0
                ? `paid ${result.netLabel}`
                : result.netAmount < 0 ? `received ${result.netLabel}` : "made an even exchange";
            ui.notifications.info(`${result.walletName} ${settlement}.`);
        } catch (error) {
            console.error("Augur Nexus | Shop trade failed", error);
            ui.notifications.error(error?.message || "The trade could not be completed.");
        } finally {
            this.#busy = false;
            this.render({ parts: ["content"] });
        }
    }

    async #preparePurchaseEntries() {
        const entries = await Promise.all(this.#shop.inventory.map(async row => {
            const key = this.#productKey(row);
            const product = this.#products.get(key);
            if (!product) return null;
            const selected = this.#purchaseCart.get(key) || 0;
            const infinite = !this.#shop.configuration.finiteStock;
            const quote = await ShopQuoteService.quoteCustomerPurchase({ shop: this.#shop, row, product });
            return {
                key, row, product, selected, infinite, quote, priceEach: quote.amount,
                remaining: infinite ? Infinity : Math.max(0, row.quantity - selected)
            };
        }));
        return entries.filter(Boolean);
    }

    #prepareSaleEntries(actorItems) {
        return actorItems.map(item => {
            const selected = this.#saleCart.get(item.itemId) || 0;
            return { ...item, selected, remaining: Math.max(0, item.quantity - selected) };
        });
    }

    #realPurchaseCard(entry) {
        const card = {
            cardId: `shop:${entry.key}`,
            action: "transferPurchase",
            providerId: entry.product.providerId,
            productId: entry.product.productId,
            name: entry.product.name,
            imageSrc: entry.product.imageSrc,
            quantity: entry.infinite ? "\u221e" : entry.remaining,
            priceLabel: this.#formatPrice(entry.priceEach),
            temporary: false,
            disabled: this.#busy,
            priceClass: entry.quote.priceClass,
            directionClass: "is-shop-source",
            ariaLabel: `Buy one ${entry.product.name} for ${this.#formatPrice(entry.priceEach)}`
        };
        return this.#withTooltip(card, entry.product, {
            priceLabel: `Buy for ${this.#formatPrice(entry.priceEach)} · Base ${this.#formatPrice(entry.quote.listedAmount)}`,
            quoteReason: entry.quote.reason,
            fallbackUuid: entry.product.sourceData?.itemData ? "" : entry.product.sourceData?.sourceUuid
        });
    }

    #incomingPurchaseCard(entry) {
        const card = {
            cardId: `incoming:${entry.key}`,
            action: "returnPurchase",
            providerId: entry.product.providerId,
            productId: entry.product.productId,
            name: entry.product.name,
            imageSrc: entry.product.imageSrc,
            quantity: entry.selected,
            priceLabel: this.#formatPrice(entry.priceEach),
            temporary: true,
            flowLabel: "Incoming",
            directionClass: "is-incoming-purchase",
            priceClass: entry.quote.priceClass,
            ariaLabel: `Return one previewed ${entry.product.name} to the Shop`
        };
        return this.#withTooltip(card, entry.product, {
            priceLabel: `${entry.selected} incoming \u00b7 ${this.#formatPrice(entry.priceEach)} each`,
            quoteReason: entry.quote.reason,
            fallbackUuid: entry.product.sourceData?.itemData ? "" : entry.product.sourceData?.sourceUuid
        });
    }

    #realSaleCard(entry) {
        const card = {
            cardId: `actor:${entry.itemId}`,
            action: entry.eligible ? "transferSale" : "",
            itemId: entry.itemId,
            name: entry.name,
            imageSrc: entry.imageSrc,
            quantity: entry.remaining,
            priceLabel: entry.eligible ? this.#formatPrice(entry.saleAmount) : "Not accepted",
            temporary: false,
            disabled: !entry.eligible || this.#busy,
            equipped: entry.equipped,
            priceClass: entry.priceClass,
            attuned: entry.attuned,
            directionClass: "is-player-source",
            ariaLabel: entry.eligible
                ? `Offer one ${entry.name} for ${this.#formatPrice(entry.saleAmount)}`
                : `${entry.name}: ${entry.reason}`
        };
        return this.#withTooltip(card, entry, {
            priceLabel: entry.eligible
                ? `Shop pays ${this.#formatPrice(entry.saleAmount)} · Base ${this.#formatPrice(entry.baseAmount)}`
                : entry.reason,
            quoteReason: entry.reason,
            fallbackUuid: entry.uuid
        });
    }

    #incomingSaleCard(entry) {
        const card = {
            cardId: `offered:${entry.itemId}`,
            action: "returnSale",
            itemId: entry.itemId,
            name: entry.name,
            imageSrc: entry.imageSrc,
            quantity: entry.selected,
            priceLabel: this.#formatPrice(entry.saleAmount),
            temporary: true,
            flowLabel: "Offered",
            directionClass: "is-incoming-sale",
            priceClass: entry.priceClass,
            ariaLabel: `Return one offered ${entry.name} to the player inventory`
        };
        return this.#withTooltip(card, entry, {
            priceLabel: `${entry.selected} offered \u00b7 ${this.#formatPrice(entry.saleAmount)} each`,
            quoteReason: entry.reason,
            fallbackUuid: entry.uuid
        });
    }

    #withTooltip(card, source, { priceLabel = "", quoteReason = "", fallbackUuid = "" } = {}) {
        const tooltipKey = card.cardId;
        this.#tooltips.set(tooltipKey, {
            uuid: String(fallbackUuid || source.uuid || ""),
            name: source.name || card.name,
            imageSrc: source.imageSrc || source.img || card.imageSrc,
            category: source.category || source.type || "Merchandise",
            description: [source.description, quoteReason || source.reason].filter(Boolean).join(" Market quote: "),
            weight: source.weight,
            priceLabel
        });
        return { ...card, tooltipKey };
    }

    async #loadProducts() {
        this.#products.clear();
        const products = await Promise.all(this.#shop.inventory.map(row =>
            ShopProductProviderRegistry.resolve(row.providerId, row.productId, { shop: this.#shop, row })
        ));
        this.#shop.inventory.forEach((row, index) => {
            if (products[index]) this.#products.set(this.#productKey(row), products[index]);
        });
    }

    #changePurchase(target, change) {
        if (this.#busy) return;
        const key = this.#productKey(target?.dataset || {});
        const row = this.#shop.inventory.find(candidate => this.#productKey(candidate) === key);
        if (!row) return;
        const current = this.#purchaseCart.get(key) || 0;
        let next;
        if (change === "none") next = 0;
        else if (change === "all") next = this.#shop.configuration.finiteStock ? row.quantity : current + 1;
        else next = current + change;
        next = Math.max(0, next);
        if (this.#shop.configuration.finiteStock) next = Math.min(row.quantity, next);
        if (next) this.#purchaseCart.set(key, next);
        else this.#purchaseCart.delete(key);
        this.render({ parts: ["content"] });
    }

    #changeSale(target, change) {
        if (this.#busy) return;
        const itemId = String(target?.dataset?.itemId || "");
        const item = this.#inventoryActor?.items?.get(itemId);
        if (!item) return;
        const current = this.#saleCart.get(itemId) || 0;
        const maximum = Math.max(1, Math.floor(Number(this.#adapter?.getItemQuantity(item)) || 1));
        let next;
        if (change === "none") next = 0;
        else if (change === "all") next = maximum;
        else next = current + change;
        next = Math.min(maximum, Math.max(0, next));
        if (next) this.#saleCart.set(itemId, next);
        else this.#saleCart.delete(itemId);
        this.render({ parts: ["content"] });
    }

    #activateTooltips() {
        for (const card of this.element.querySelectorAll("[data-shop-tooltip]")) {
            const tooltip = this.#tooltips.get(card.dataset.shopTooltip);
            if (!tooltip) continue;
            const nativeTooltip = this.#adapter?.getItemTooltipAttributes?.(tooltip);
            if (nativeTooltip) {
                card.classList.add(...(nativeTooltip.classes || []));
                card.dataset.tooltip = nativeTooltip.tooltip;
                card.dataset.tooltipClass = nativeTooltip.tooltipClass;
            } else {
                card.dataset.tooltip = this.#genericTooltip(tooltip);
                card.dataset.tooltipClass = "nexus-shop-rich-tooltip";
            }
            card.dataset.tooltipDirection = card.closest(".nexus-shop-actor-inventory") ? "RIGHT" : "LEFT";
            card.addEventListener("mousedown", event => {
                if (event.button === 1) event.preventDefault();
            });
        }
    }

    #genericTooltip(tooltip) {
        const clean = value => foundry.utils.escapeHTML(String(value || ""));
        const description = clean(String(tooltip.description || "").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim());
        const weight = Number(tooltip.weight?.value);
        const weightLabel = Number.isFinite(weight) && weight > 0
            ? `<span><i class="fas fa-weight-hanging"></i> ${weight} ${clean(tooltip.weight?.units || "")}</span>`
            : "";
        return `<section class="nexus-shop-tooltip-card">
            <header><img src="${clean(tooltip.imageSrc)}" alt=""><div><strong>${clean(tooltip.name)}</strong><span>${clean(tooltip.category)}</span></div></header>
            <div class="nexus-shop-tooltip-meta"><strong>${clean(tooltip.priceLabel)}</strong>${weightLabel}</div>
            ${description ? `<p>${description}</p>` : ""}
            <small><i class="fas fa-computer-mouse"></i> Middle-click to lock</small>
        </section>`;
    }

    #activateDropZone(selector, role) {
        const zone = this.element.querySelector(selector);
        zone?.addEventListener("dragover", event => { event.preventDefault(); zone.classList.add("is-dragover"); });
        zone?.addEventListener("dragleave", () => zone.classList.remove("is-dragover"));
        zone?.addEventListener("drop", event => this.#onActorDrop(event, role));
    }

    async #onActorDrop(event, role) {
        event.preventDefault();
        event.currentTarget.classList.remove("is-dragover");
        const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
        const actor = data.uuid ? await fromUuid(data.uuid).catch(() => null) : game.actors.get(data.id);
        if (role === "wallet") await this.#setWallet(actor);
        else await this.#setInventoryActor(actor);
    }

    async #setWallet(actor) {
        try {
            await this.#adapter.rememberWallet(actor);
            this.#wallet = actor;
            if (!this.#inventoryActor) this.#inventoryActor = actor;
            this.render({ parts: ["content"] });
        } catch (error) { ui.notifications.warn(error?.message || "That Actor cannot provide the Shop wallet."); }
    }

    async #setInventoryActor(actor) {
        try {
            if (!this.#wallet) throw new Error("Choose a wallet before selecting a trading inventory.");
            const wasSameActor = this.#inventoryActor?.uuid === this.#wallet?.uuid;
            await this.#adapter.rememberInventoryHolder(this.#wallet, actor);
            this.#inventoryActor = actor;
            if (wasSameActor && this.#adapter.isEligibleWallet(actor, game.user)) {
                await this.#adapter.rememberWallet(actor);
                this.#wallet = actor;
            }
            this.#saleCart.clear();
            this.render({ parts: ["content"] });
        } catch (error) { ui.notifications.warn(error?.message || "That Actor cannot trade inventory here."); }
    }

    #reconcileCarts(actorItems) {
        const stock = new Map(this.#shop.inventory.map(row => [this.#productKey(row), row.quantity]));
        for (const [key, quantity] of this.#purchaseCart) {
            if (!this.#products.has(key)) this.#purchaseCart.delete(key);
            else if (this.#shop.configuration.finiteStock && quantity > (stock.get(key) || 0)) {
                const available = stock.get(key) || 0;
                if (available) this.#purchaseCart.set(key, available);
                else this.#purchaseCart.delete(key);
            }
        }
        const items = new Map(actorItems.map(item => [item.itemId, item]));
        for (const [itemId, quantity] of this.#saleCart) {
            const item = items.get(itemId);
            if (!item?.eligible) this.#saleCart.delete(itemId);
            else if (quantity > item.quantity) this.#saleCart.set(itemId, item.quantity);
        }
    }

    #purchaseCount() { return Array.from(this.#purchaseCart.values()).reduce((sum, quantity) => sum + quantity, 0); }
    #saleCount() { return Array.from(this.#saleCart.values()).reduce((sum, quantity) => sum + quantity, 0); }
    #formatPrice(amount) { return this.#adapter?.formatPrice(amount) || String(amount); }
    #productKey(line) { return `${line.providerId || ""}::${line.productId || ""}`; }
    #matchesShop(value) { return Boolean(value && (value === this.#shopId || value === this.#shop?.id || value === this.#shop?.uuid)); }
}

