import { ShopSystemAdapterRegistry } from "../../services/ShopSystemAdapterRegistry.js";
import { PF2E_ACQUIRED_PROVIDER_ID, PF2E_COMPENDIUM_PROVIDER_ID } from "./Pf2eItemProvider.js";

const MODULE_ID = "augur-nexus";
const WALLET_FLAG = "tradingActorUuid";
const INVENTORY_FLAG = "tradingInventoryByWallet";
const WALLET_TYPES = new Set(["character", "npc", "loot", "party", "vehicle"]);
const INVENTORY_TYPES = new Set(["character", "npc", "loot", "party", "vehicle"]);
const PHYSICAL_TYPES = new Set(["ammo", "armor", "backpack", "book", "consumable", "equipment", "shield", "treasure", "weapon", "kit"]);

function Coins(data = {}) {
    const CoinClass = game.pf2e?.Coins;
    if (!CoinClass) throw new Error("The Pathfinder 2e coin API is unavailable.");
    return new CoinClass(data);
}

function priceInCopper(item) {
    const CoinClass = game.pf2e?.Coins;
    const price = item?.price || item?.system?.price;
    if (!CoinClass || !price) return 0;
    return Math.max(0, Math.round(CoinClass.fromPrice(price, 1).copperValue));
}

function bulkValue(item) {
    const lightUnits = item?.bulk?.toLightUnits?.();
    if (Number.isFinite(lightUnits)) return lightUnits / 10;
    const value = item?.system?.bulk?.value;
    if (value === "L") return 0.1;
    if (value === "-" || value === null || value === undefined) return 0;
    return Math.max(0, Number(value) || 0);
}

function cleanItemSource(item, quantity) {
    const itemData = foundry.utils.deepClone(item.toObject());
    delete itemData._id;
    delete itemData.folder;
    delete itemData.sort;
    delete itemData.ownership;
    itemData.system ||= {};
    itemData.system.quantity = quantity;
    itemData.system.containerId = null;
    if (itemData.system.equipped && typeof itemData.system.equipped === "object") {
        itemData.system.equipped.carryType = "worn";
        itemData.system.equipped.handsHeld = 0;
        itemData.system.equipped.inSlot = false;
        if ("invested" in itemData.system.equipped) itemData.system.equipped.invested = false;
    }
    return itemData;
}

export const Pf2eTradeAdapter = {
    id: "pf2e",
    label: "Pathfinder 2e",

    isAvailable() {
        return game.system?.id === "pf2e" && Boolean(game.pf2e?.Coins);
    },

    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.isOwnedInventoryActor(actor, user, WALLET_TYPES)
            && typeof actor.inventory?.removeCoins === "function"
            && typeof actor.inventory?.addCoins === "function";
    },

    isEligibleInventoryHolder(actor, user = game.user) {
        return this.isOwnedInventoryActor(actor, user, INVENTORY_TYPES)
            && typeof actor.inventory?.add === "function";
    },

    isOwnedInventoryActor(actor, user, types) {
        if (!actor || actor.documentName !== "Actor" || !types.has(actor.type) || !actor.inventory) 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 Pathfinder actor that can hold coins.");
        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 Pathfinder 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 = "") {
        const groups = [
            { id: "character", label: "Characters" },
            { id: "npc", label: "NPCs" },
            { id: "party", label: "Parties" },
            { id: "loot", label: "Loot Actors" },
            { id: "vehicle", label: "Vehicles" }
        ];
        return groups.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;
        const bulk = actor.inventory?.bulk;
        const lightUnits = bulk?.value?.toLightUnits?.();
        const value = Number.isFinite(lightUnits) ? lightUnits / 10 : Number(bulk?.bulk);
        const max = Number(bulk?.max);
        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() : "unlimited"} Bulk`
                : "Bulk unavailable",
            weightValue: Number.isFinite(value) ? value : null,
            weightMax: Number.isFinite(max) ? max : null,
            weightUnits: "Bulk"
        };
    },

    getWalletValue(actor) {
        return Math.max(0, Math.round(Number(actor?.inventory?.coins?.copperValue) || 0));
    },

    async getInventoryItems(actor, { shop } = {}) {
        if (!actor) return [];
        const products = [];
        for (const item of actor.inventory || []) {
            if (!this.isTradeItem(item)) continue;
            const quote = await this.getSaleQuote(item, { actor, shop });
            const quantity = this.getItemQuantity(item);
            products.push({
                itemId: item.id,
                uuid: item.uuid,
                name: item.name,
                imageSrc: item.img,
                type: item.type,
                quantity,
                equipped: item.isEquipped === true,
                attuned: false,
                eligible: quote.eligible,
                reason: quote.reason || "",
                baseAmount: quote.baseAmount,
                saleAmount: Math.max(0, Math.floor(quote.baseAmount * (shop?.configuration?.customerSaleMultiplier ?? 0.5))),
                weight: { value: bulkValue(item), units: "Bulk" }
            });
        }
        return products.sort((a, b) => a.name.localeCompare(b.name));
    },

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

    isTradeItem(item) {
        if (!item || !PHYSICAL_TYPES.has(item.type)) return false;
        return !(item.isCoinage || item.isCurrency || (item.type === "treasure" && item.system?.category === "coin"));
    },

    async getSaleQuote(item, { actor } = {}) {
        if (!this.isTradeItem(item)) return { eligible: false, reason: "This is not a tradeable physical item.", baseAmount: 0 };
        if (item.isTemporary || item.system?.temporary === true) {
            return { eligible: false, reason: "Temporary items cannot be sold.", baseAmount: 0 };
        }
        if (item.isIdentified === false || item.system?.identification?.status === "unidentified") {
            return { eligible: false, reason: "Identify this item before selling it.", baseAmount: 0 };
        }
        if (item.type === "backpack") {
            const hasContents = Boolean(item.contents?.size)
                || Boolean(actor?.items?.find(candidate => candidate.system?.containerId === item.id));
            if (hasContents) return { eligible: false, reason: "Empty this container before selling it.", baseAmount: 0 };
        }
        const baseAmount = priceInCopper(item);
        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));
        return Coins({ cp: amount }).toString();
    },

    canAfford(actor, copperPieces) {
        return this.getWalletValue(actor) >= Math.max(0, Math.round(Number(copperPieces) || 0));
    },

    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) throw new Error(`${product.name || "This product"} no longer has a Pathfinder item source.`);
        delete itemData._id;
        delete itemData.folder;
        delete itemData.sort;
        delete itemData.ownership;
        itemData.system ||= {};
        itemData.system.quantity = quantity;
        itemData.system.containerId = null;
        if (itemData.system.equipped && typeof itemData.system.equipped === "object") {
            itemData.system.equipped.carryType = "worn";
            itemData.system.equipped.handsHeld = 0;
            itemData.system.equipped.inSlot = false;
            if ("invested" in itemData.system.equipped) itemData.system.equipped.invested = false;
        }
        itemData.flags ||= {};
        itemData.flags[MODULE_ID] ||= {};
        itemData.flags[MODULE_ID].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 originalInventory = new Map(inventoryActor.items.map(item => [item.id, foundry.utils.deepClone(item.toObject())]));
        const originalSales = new Map(sales.map(sale => [sale.item.id, foundry.utils.deepClone(sale.item.toObject())]));
        const originalCoins = wallet.items
            .filter(item => item.isCoinage || item.isCurrency)
            .map(item => foundry.utils.deepClone(item.toObject()));
        let addedItems = [];
        let currencyTouched = false;

        return {
            apply: async () => {
                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 (purchasedItemData.length) {
                    addedItems = await inventoryActor.inventory.add(purchasedItemData, { stack: true });
                }
                if (netAmount > 0) {
                    currencyTouched = true;
                    const paid = await wallet.inventory.removeCoins(Coins({ cp: netAmount }), { byValue: true });
                    if (!paid) throw new Error(`${wallet.name} could not pay the Shop.`);
                } else if (netAmount < 0) {
                    currencyTouched = true;
                    await wallet.inventory.addCoins(Coins({ cp: Math.abs(netAmount) }));
                }
            },
            rollback: async () => {
                const addedIds = new Set(addedItems.map(item => item.id));
                const newIds = Array.from(addedIds).filter(id => !originalInventory.has(id) && inventoryActor.items.has(id));
                if (newIds.length) await inventoryActor.deleteEmbeddedDocuments("Item", newIds);

                const restoreSources = new Map();
                for (const id of [...addedIds, ...originalSales.keys()]) {
                    const source = originalInventory.get(id) || originalSales.get(id);
                    if (source) restoreSources.set(id, source);
                }
                const updates = [];
                const recreations = [];
                for (const [id, source] of restoreSources) {
                    if (inventoryActor.items.has(id)) {
                        updates.push({ _id: id, "system.quantity": source.system?.quantity ?? 1 });
                    } else {
                        recreations.push(source);
                    }
                }
                if (updates.length) await inventoryActor.updateEmbeddedDocuments("Item", updates);
                if (recreations.length) await inventoryActor.createEmbeddedDocuments("Item", recreations, { keepId: true });

                if (currencyTouched) {
                    const currentCoinIds = wallet.items.filter(item => item.isCoinage || item.isCurrency).map(item => item.id);
                    if (currentCoinIds.length) await wallet.deleteEmbeddedDocuments("Item", currentCoinIds);
                    if (originalCoins.length) await wallet.createEmbeddedDocuments("Item", originalCoins, { keepId: true });
                }
            }
        };
    },

    async createStockProduct(item) {
        if (!item || item.documentName !== "Item" || !this.isTradeItem(item)) {
            throw new Error("Only physical Pathfinder items can be added to Shop stock.");
        }
        if (item.pack || item.uuid?.startsWith("Compendium.")) {
            return {
                providerId: PF2E_COMPENDIUM_PROVIDER_ID,
                productId: item.uuid,
                quantity: 1,
                sourceUuid: item.uuid
            };
        }
        const productId = foundry.utils.randomID();
        return {
            providerId: PF2E_ACQUIRED_PROVIDER_ID,
            productId,
            quantity: 1,
            sourceUuid: item.uuid,
            productData: {
                itemData: cleanItemSource(item, 1),
                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();
        return {
            providerId: PF2E_ACQUIRED_PROVIDER_ID,
            productId,
            quantity,
            productData: {
                itemData: cleanItemSource(item, quantity),
                sourceUuid: item._stats?.compendiumSource || item.flags?.core?.sourceId || "",
                sellerUuid: context.sellerUuid || "",
                transactionId: context.transactionId || "",
                acquisitionPrice: context.acquisitionPrice || 0
            }
        };
    }
};

export function registerPf2eTradeAdapter() {
    ShopSystemAdapterRegistry.register(Pf2eTradeAdapter);
}
