import { ShopSystemAdapterRegistry } from "../../services/ShopSystemAdapterRegistry.js";
import { DND5E_ACQUIRED_PROVIDER_ID, DND5E_COMPENDIUM_PROVIDER_ID } from "./Dnd5eItemProvider.js";

const MODULE_ID = "augur-nexus";
const WALLET_FLAG = "tradingActorUuid";
const INVENTORY_FLAG = "tradingInventoryByWallet";
const PHYSICAL_TYPES = new Set(["weapon", "equipment", "consumable", "container", "tool", "loot"]);
const COIN_VALUES = { pp: 1000, gp: 100, ep: 50, sp: 10, cp: 1 };

export const Dnd5eTradeAdapter = {
    id: "dnd5e",
    label: "D&D 5e",

    isAvailable() {
        return game.system?.id === "dnd5e" && Boolean(globalThis.dnd5e?.applications?.CurrencyManager);
    },

    assignmentKey(uuid) {
        return String(uuid || "").replaceAll(".", "%2E");
    },

    async getDefaultWallet() {
        const rememberedUuid = game.user?.getFlag(MODULE_ID, WALLET_FLAG);
        const remembered = rememberedUuid ? await this.resolveActor(rememberedUuid) : null;
        if (this.isEligibleWallet(remembered, game.user)) return remembered;
        if (this.isEligibleWallet(game.user?.character, game.user)) return game.user.character;
        const owned = this.getEligibleWallets(game.user);
        return owned.length === 1 ? owned[0] : null;
    },

    async getDefaultInventoryHolder(wallet) {
        if (!wallet) return null;
        const assignments = game.user?.getFlag(MODULE_ID, INVENTORY_FLAG) || {};
        const uuid = assignments[this.assignmentKey(wallet.uuid)];
        const remembered = uuid ? await this.resolveActor(uuid) : null;
        return this.isEligibleInventoryHolder(remembered, game.user) ? remembered : wallet;
    },

    getEligibleWallets(user = game.user) {
        return game.actors.filter(actor => this.isEligibleWallet(actor, user)).sort((a, b) => a.name.localeCompare(b.name));
    },

    getEligibleInventoryHolders(user = game.user) {
        return game.actors.filter(actor => this.isEligibleInventoryHolder(actor, user)).sort((a, b) => a.name.localeCompare(b.name));
    },

    isEligibleWallet(actor, user = game.user) {
        return this.isOwnedActorOfType(actor, user, ["character", "vehicle"]);
    },

    isEligibleInventoryHolder(actor, user = game.user) {
        return this.isOwnedActorOfType(actor, user, ["character", "vehicle", "npc"]);
    },

    isOwnedActorOfType(actor, user, types) {
        if (!actor || actor.documentName !== "Actor" || !types.includes(actor.type)) return false;
        return user?.isGM || actor.testUserPermission(user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER);
    },

    async rememberWallet(actor) {
        if (!this.isEligibleWallet(actor, game.user)) throw new Error("Choose an owned character or vehicle wallet.");
        await game.user.setFlag(MODULE_ID, WALLET_FLAG, actor.uuid);
        return actor;
    },

    async rememberInventoryHolder(wallet, actor) {
        if (!this.isEligibleWallet(wallet, game.user)) throw new Error("Select a wallet first.");
        if (!this.isEligibleInventoryHolder(actor, game.user)) throw new Error("Choose an owned character, vehicle, or NPC inventory.");
        const assignments = { ...(game.user?.getFlag(MODULE_ID, INVENTORY_FLAG) || {}) };
        assignments[this.assignmentKey(wallet.uuid)] = actor.uuid;
        await game.user.setFlag(MODULE_ID, INVENTORY_FLAG, assignments);
        return actor;
    },

    async resolveActor(uuid) {
        if (!uuid) return null;
        const actor = await fromUuid(uuid).catch(() => null);
        return actor?.documentName === "Actor" ? actor : null;
    },

    groupActors(actors, selectedUuid = "") {
        return [
            { id: "character", label: "Characters" },
            { id: "vehicle", label: "Vehicles" },
            { id: "npc", label: "Dependents" }
        ].map(group => ({
            ...group,
            actors: actors.filter(actor => actor.type === group.id).map(actor => ({
                uuid: actor.uuid,
                name: actor.name,
                img: actor.img,
                selected: actor.uuid === selectedUuid
            }))
        })).filter(group => group.actors.length);
    },

    async getActorView(actor) {
        if (!actor) return null;
        let encumbrance = actor.system?.attributes?.encumbrance;
        if (actor.type === "vehicle" && typeof actor.system?.getEncumbrance === "function") {
            encumbrance = await actor.system.getEncumbrance();
        }
        const value = Number(encumbrance?.value);
        const max = Number(encumbrance?.max);
        const units = game.settings.get("dnd5e", "metricWeightUnits") ? "kg" : "lb";
        const totalCopper = this.getWalletValue(actor);
        return {
            uuid: actor.uuid,
            name: actor.name,
            img: actor.img,
            type: actor.type,
            typeLabel: game.i18n.localize(CONFIG.Actor.typeLabels[actor.type] || actor.type),
            wallet: this.formatPrice(totalCopper),
            walletValue: totalCopper,
            weight: Number.isFinite(value)
                ? `${value.toLocaleString()} / ${Number.isFinite(max) ? max.toLocaleString() : "∞"} ${units}`
                : "Weight unavailable",
            weightValue: Number.isFinite(value) ? value : null,
            weightMax: Number.isFinite(max) ? max : null,
            weightUnits: units
        };
    },

    getWalletValue(actor) {
        const currency = actor?.system?.currency || {};
        return Object.entries(COIN_VALUES).reduce((total, [denomination, copper]) =>
            total + (Math.max(0, Number(currency[denomination]) || 0) * copper), 0);
    },

    async getInventoryItems(actor, { shop } = {}) {
        if (!actor) return [];
        const childrenByContainer = new Map();
        for (const item of actor.items) {
            const containerId = item.system?.container;
            if (containerId) childrenByContainer.set(containerId, (childrenByContainer.get(containerId) || 0) + 1);
        }
        const products = [];
        for (const item of actor.items) {
            if (!PHYSICAL_TYPES.has(item.type)) continue;
            const quote = await this.getSaleQuote(item, { actor, shop, childrenByContainer });
            const quantity = Math.max(1, Math.floor(Number(item.system?.quantity) || 1));
            products.push({
                itemId: item.id,
                uuid: item.uuid,
                name: item.name,
                imageSrc: item.img,
                type: item.type,
                quantity,
                equipped: item.system?.equipped === true,
                attuned: item.system?.attuned === true,
                eligible: quote.eligible,
                reason: quote.reason || "",
                baseAmount: quote.baseAmount,
                saleAmount: Math.max(0, Math.floor(quote.baseAmount * (shop?.configuration?.customerSaleMultiplier ?? 0.75))),
                weight: item.system?.weight || { value: 0, units: "lb" }
            });
        }
        return products.sort((a, b) => a.name.localeCompare(b.name));
    },

    getItemQuantity(item) {
        return Math.max(1, Math.floor(Number(item?.system?.quantity) || 1));
    },

    async getSaleQuote(item, { actor, childrenByContainer } = {}) {
        if (!item || !PHYSICAL_TYPES.has(item.type)) return { eligible: false, reason: "This is not a physical inventory item.", baseAmount: 0 };
        const childCount = childrenByContainer?.get(item.id)
            ?? actor?.items?.filter(candidate => candidate.system?.container === item.id).length
            ?? 0;
        if (item.type === "container" && childCount > 0) {
            return { eligible: false, reason: "Empty this container before selling it.", baseAmount: 0 };
        }
        const price = item.system?.price || {};
        const baseAmount = Math.max(0, Math.round((Number(price.value) || 0) * (COIN_VALUES[price.denomination] || 0)));
        if (!baseAmount) return { eligible: false, reason: "This item has no listed value.", baseAmount: 0 };
        return { eligible: true, baseAmount };
    },

    formatPrice(copperPieces) {
        const amount = Math.max(0, Math.round(Number(copperPieces) || 0));
        const format = value => Number(value.toFixed(2)).toLocaleString(undefined, { maximumFractionDigits: 2 });
        if (amount >= 100) return `${format(amount / 100)} gp`;
        if (amount >= 10) return `${format(amount / 10)} sp`;
        return `${amount} cp`;
    },

    getItemTooltipAttributes(tooltip) {
        if (!tooltip?.uuid) return null;
        return {
            classes: ["item-tooltip"],
            tooltip: `<section class="loading" data-uuid="${foundry.utils.escapeHTML(tooltip.uuid)}"><i class="fas fa-spinner fa-spin-pulse"></i></section>`,
            tooltipClass: "dnd5e2 dnd5e-tooltip item-tooltip themed theme-light"
        };
    },


    getCurrencyDebitUpdate(actor, copperPieces) {
        const manager = globalThis.dnd5e?.applications?.CurrencyManager;
        if (!manager) throw new Error("The D&D 5e currency manager is unavailable.");
        return manager.getActorCurrencyUpdates(actor, copperPieces, "cp", {
            exact: false,
            makeChange: true,
            priority: "low"
        });
    },

    getCurrencyCreditUpdate(actor, copperPieces) {
        let remainder = Math.max(0, Math.round(Number(copperPieces) || 0));
        const currency = foundry.utils.deepClone(actor.system?.currency || {});
        for (const [denomination, value] of [["gp", 100], ["sp", 10], ["cp", 1]]) {
            const coins = Math.floor(remainder / value);
            currency[denomination] = Math.max(0, Number(currency[denomination]) || 0) + coins;
            remainder -= coins * value;
        }
        return { "system.currency": currency };
    },

    canAfford(actor, copperPieces) {
        return !this.getCurrencyDebitUpdate(actor, copperPieces).remainder;
    },

    async buildItemData(product, quantity, context = {}) {
        let itemData = product.sourceData?.itemData
            ? foundry.utils.deepClone(product.sourceData.itemData)
            : null;
        if (!itemData && product.sourceData?.sourceUuid) {
            const source = await fromUuid(product.sourceData.sourceUuid).catch(() => null);
            if (source?.documentName === "Item") itemData = source.toObject();
        }
        if (!itemData) {
            const source = product.sourceData || {};
            itemData = {
                name: product.lotLabel || product.name,
                type: "loot",
                img: product.imageSrc,
                system: {
                    quantity,
                    weight: { value: Number(product.weight?.value || 0), units: product.weight?.units || "lb" },
                    price: { value: Number(product.price?.amount || 0) / 100, denomination: "gp" },
                    description: { value: `<p>${foundry.utils.escapeHTML(product.description || "")}</p>` },
                    type: { value: "trade" }
                },
                flags: { [MODULE_ID]: { tradeGood: true, tradeGoodId: product.productId } }
            };
        }
        delete itemData._id;
        itemData.system ||= {};
        itemData.system.quantity = quantity;
        itemData.system.container = null;
        itemData.flags ||= {};
        itemData.flags["augur-nexus"] ||= {};
        itemData.flags["augur-nexus"].shopProduct = {
            providerId: product.providerId,
            productId: product.productId,
            shopUuid: context.shopUuid || "",
            transactionId: context.transactionId || "",
            walletUuid: context.walletUuid || "",
            inventoryUuid: context.inventoryUuid || "",
            acquisitionPrice: context.priceEach || 0
        };
        return itemData;
    },

    async createActorTradeSession({ wallet, inventoryActor, purchasedItemData = [], sales = [], netAmount = 0 } = {}) {
        const originalCurrency = foundry.utils.deepClone(wallet.system?.currency || {});
        const originalItems = sales.map(sale => foundry.utils.deepClone(sale.item.toObject()));
        let createdItems = [];

        return {
            apply: async () => {
                if (purchasedItemData.length) {
                    createdItems = await inventoryActor.createEmbeddedDocuments("Item", purchasedItemData);
                }
                const quantityUpdates = [];
                const deletions = [];
                for (const sale of sales) {
                    const remaining = sale.available - sale.line.quantity;
                    if (remaining > 0) quantityUpdates.push({ _id: sale.item.id, "system.quantity": remaining });
                    else deletions.push(sale.item.id);
                }
                if (quantityUpdates.length) await inventoryActor.updateEmbeddedDocuments("Item", quantityUpdates);
                if (deletions.length) await inventoryActor.deleteEmbeddedDocuments("Item", deletions);

                if (netAmount > 0) {
                    const debit = this.getCurrencyDebitUpdate(wallet, netAmount);
                    const { item: _items, remainder: _remainder, ...walletUpdate } = debit;
                    await wallet.update(walletUpdate);
                } else if (netAmount < 0) {
                    await wallet.update(this.getCurrencyCreditUpdate(wallet, Math.abs(netAmount)));
                }
            },
            rollback: async () => {
                if (createdItems.length) {
                    await inventoryActor.deleteEmbeddedDocuments("Item", createdItems.map(item => item.id));
                }
                const originalIds = new Set(originalItems.map(item => item._id));
                const existingOriginals = inventoryActor.items.filter(item => originalIds.has(item.id));
                if (existingOriginals.length) {
                    await inventoryActor.updateEmbeddedDocuments("Item", existingOriginals.map(item => {
                        const original = originalItems.find(candidate => candidate._id === item.id);
                        return { _id: item.id, "system.quantity": original.system?.quantity ?? 1 };
                    }));
                }
                const missing = originalItems.filter(item => !inventoryActor.items.has(item._id));
                if (missing.length) await inventoryActor.createEmbeddedDocuments("Item", missing, { keepId: true });
                await wallet.update({ "system.currency": originalCurrency });
            }
        };
    },

    async createStockProduct(item) {
        if (!item || item.documentName !== "Item" || !PHYSICAL_TYPES.has(item.type)) {
            throw new Error("Only physical D&D items can be added to Shop stock.");
        }
        if (item.pack || item.uuid?.startsWith("Compendium.")) {
            return {
                providerId: DND5E_COMPENDIUM_PROVIDER_ID,
                productId: item.uuid,
                quantity: 1,
                sourceUuid: item.uuid
            };
        }
        const productId = foundry.utils.randomID();
        const itemData = foundry.utils.deepClone(item.toObject());
        delete itemData._id;
        delete itemData.folder;
        delete itemData.sort;
        delete itemData.ownership;
        itemData.system ||= {};
        itemData.system.quantity = 1;
        itemData.system.container = null;
        if ("equipped" in itemData.system) itemData.system.equipped = false;
        if ("attuned" in itemData.system) itemData.system.attuned = false;
        return {
            providerId: DND5E_ACQUIRED_PROVIDER_ID,
            productId,
            quantity: 1,
            sourceUuid: item.uuid,
            productData: {
                itemData,
                sourceUuid: item.uuid,
                compendiumSource: item._stats?.compendiumSource || item.flags?.core?.sourceId || "",
                stockedByUserId: game.user?.id || ""
            }
        };
    },

    async createSaleProduct(item, quantity, context = {}) {
        const productId = foundry.utils.randomID();
        const itemData = foundry.utils.deepClone(item.toObject());
        delete itemData._id;
        itemData.system ||= {};
        itemData.system.quantity = quantity;
        itemData.system.container = null;
        if ("equipped" in itemData.system) itemData.system.equipped = false;
        if ("attuned" in itemData.system) itemData.system.attuned = false;
        return {
            providerId: DND5E_ACQUIRED_PROVIDER_ID,
            productId,
            quantity,
            productData: {
                itemData,
                sourceUuid: item._stats?.compendiumSource || item.flags?.core?.sourceId || "",
                sellerUuid: context.sellerUuid || "",
                transactionId: context.transactionId || "",
                acquisitionPrice: context.acquisitionPrice || 0
            }
        };
    }
};

export function registerDnd5eTradeAdapter() {
    ShopSystemAdapterRegistry.register(Dnd5eTradeAdapter);
}
