import { ShopProductProviderRegistry } from "../../services/ShopProductProviderRegistry.js";

export const DND5E_COMPENDIUM_PROVIDER_ID = "augur-nexus:dnd5e-compendium";
export const DND5E_ACQUIRED_PROVIDER_ID = "augur-nexus:dnd5e-acquired";

const PHYSICAL_TYPES = new Set(["weapon", "equipment", "consumable", "container", "tool", "loot"]);
const BLOCKED_PACK_PATTERN = /(?:^|[._ -])(classes?|class[._ -]*features?|subclass[._ -]*features?|monster[._ -]*features?|spells?|backgrounds?|feats?|species|races?)(?:$|[._ -])/i;
const COIN_VALUES = { pp: 1000, gp: 100, ep: 50, sp: 10, cp: 1 };

function itemPriceInCopper(item) {
    const price = item?.system?.price || {};
    return Math.max(0, Math.round((Number(price.value) || 0) * (COIN_VALUES[price.denomination] || 0)));
}

function sourceBookLabel(value) {
    const source = String(value || "").trim();
    return String(CONFIG.DND5E?.sourceBooks?.[source] || source || "Unspecified Source");
}

function sourceLabelFromItem(item) {
    const source = item?.system?.source || {};
    return sourceBookLabel(source.value || source.custom || source.book);
}

function facetValue(value, fallback = "") {
    if (value && typeof value === "object") value = value.value ?? value.id ?? value.type ?? fallback;
    return String(value ?? fallback).trim().toLocaleLowerCase();
}

function productFromItem(item, { id = item.uuid, sourceUuid = item.uuid, itemData = null } = {}) {
    const properties = new Set(Array.from(item.system?.properties || []).map(value => facetValue(value)));
    const rarity = facetValue(item.system?.rarity, "mundane") || "mundane";
    return {
        id,
        name: item.name,
        lotLabel: item.name,
        category: item.type,
        typeLabel: ({ weapon: "Weapon", equipment: "Equipment", consumable: "Consumable", container: "Container", tool: "Tool", loot: "Loot" })[item.type] || item.type,
        subtypeLabel: facetValue(item.system?.type),
        sourceLabel: sourceLabelFromItem(item),
        imageSrc: item.img,
        description: String(item.system?.description?.value || ""),
        price: { amount: itemPriceInCopper(item), currencyId: "dnd5e-cp" },
        weight: { value: Number(item.system?.weight?.value || 0), units: item.system?.weight?.units || "lb" },
        cargoSlots: 1,
        sourceData: {
            sourceUuid,
            itemData: itemData ? foundry.utils.deepClone(itemData) : null,
            type: item.type,
            rarity,
            magical: rarity !== "mundane" || properties.has("mgc") || /(?:^|\s)\+[1-9](?:\s|$|,)/.test(item.name)
        }
    };
}

export class Dnd5eItemProvider {
    static #packIndexes = new Map();
    static #packCatalogs = new Map();
    static #productsById = new Map();
    static #RECOMMENDED_PACKS = new Set(["dnd5e.items", "dnd5e.tradegoods", "dnd5e.equipment24"]);
    static #INDEX_FIELDS = [
        "img", "type", "system.price", "system.weight", "system.rarity", "system.type",
        "system.properties", "system.identified", "system.source.book", "system.source.custom", "system.source.rules", "system.identifier",
        "system.container", "system.attuned", "system.attunement"
    ];

    static getCatalogSchema() {
        const labels = (values = {}) => Object.fromEntries(Object.entries(values || {}).map(([id, value]) => [id, game.i18n.localize(value?.label || value)]));
        return {
            id: "dnd5e-physical-items-v1",
            types: [
                { id: "weapon", label: "Weapon" }, { id: "equipment", label: "Equipment" },
                { id: "consumable", label: "Consumable" }, { id: "tool", label: "Tool" },
                { id: "loot", label: "Loot" }, { id: "container", label: "Container" }
            ],
            filterGroups: [
                { id: "weaponType", label: "Weapon Type", types: ["weapon"], visibility: "single", dynamic: true, optionLabels: labels(CONFIG.DND5E?.weaponTypes) },
                { id: "equipmentType", label: "Equipment Type", types: ["equipment"], visibility: "single", dynamic: true, optionLabels: labels(CONFIG.DND5E?.equipmentTypes) },
                { id: "consumableType", label: "Consumable Type", types: ["consumable"], visibility: "single", dynamic: true, optionLabels: labels(CONFIG.DND5E?.consumableTypes) },
                { id: "toolType", label: "Tool Type", types: ["tool"], visibility: "single", dynamic: true, optionLabels: labels(CONFIG.DND5E?.toolTypes) },
                { id: "lootType", label: "Loot Type", types: ["loot"], visibility: "single", dynamic: true, optionLabels: labels(CONFIG.DND5E?.lootTypes) },
                { id: "rarity", label: "Rarity", dynamic: true, optionLabels: { mundane: "Mundane", ...labels(CONFIG.DND5E?.itemRarity) } },
                { id: "properties", label: "Properties", types: ["weapon", "equipment", "consumable"], visibility: "all", dynamic: true, optionLabels: labels(CONFIG.DND5E?.itemProperties) },
                { id: "shopTags", label: "Shop Category", dynamic: true },
                { id: "rulesSource", label: "Source", dynamic: true, placement: "bottom" }
            ]
        };
    }

    static #merchandiseKind(type, subtype) {
        if (type === "weapon") return "weapon";
        if (type === "equipment" && ["light", "medium", "heavy"].includes(subtype)) return "armor";
        if (type === "equipment" && subtype === "shield") return "shield";
        if (type === "consumable" && ["ammo", "ammunition"].includes(subtype)) return "ammunition";
        return "";
    }

    static #shopTags(type, subtype, properties = [], magical = false) {
        const tags = new Set();
        const values = new Set(properties.map(value => String(value).toLocaleLowerCase()));
        if (type === "weapon") {
            const ranged = ["simpler", "martialr"].includes(subtype) || values.has("amm");
            tags.add(ranged ? "ranged-weapon" : "melee-weapon");
        }
        if (type === "consumable" && ["ammo", "ammunition"].includes(subtype)) tags.add("ammunition");
        if (type === "consumable" && ["potion", "poison"].includes(subtype)) tags.add("apothecary");
        const armor = type === "equipment" && ["light", "medium", "heavy", "shield"].includes(subtype);
        const ordinaryLoot = type === "loot" && !["gem", "art", "trade", "currency"].includes(subtype);
        if (type === "container" || type === "tool" || (type === "equipment" && !armor)
            || (type === "consumable" && subtype === "food") || ordinaryLoot) tags.add("outfitter");
        const apothecary = tags.has("apothecary");
        const arcaneType = ["consumable", "equipment", "tool", "container"].includes(type);
        if (subtype === "scroll" || (magical && arcaneType && !apothecary && !tags.has("ammunition"))) {
            tags.add("arcane-curio");
        }
        return [...tags];
    }

    static getStockTemplates() {
        return [
            {
                id: "blacksmith", name: "Blacksmith", description: "Weapons, armor, and shields.",
                count: { min: 8, max: 14 }, quantity: { min: 1, max: 3 },
                discovery: {
                    activeTypes: ["weapon", "equipment"],
                    filterState: { equipmentType: { light: 1, medium: 1, heavy: 1, shield: 1 }, rarity: { mundane: 1 } },
                    price: { minimum: null, maximum: 150000, requireListed: false }
                }
            },
            {
                id: "general-store", name: "General Store", description: "Mundane adventuring gear, tools, containers, and provisions.",
                count: { min: 10, max: 18 }, quantity: { min: 1, max: 5 },
                discovery: {
                    activeTypes: ["consumable", "container", "tool", "loot", "equipment"],
                    filterState: { rarity: { mundane: 1 } },
                    price: { minimum: null, maximum: 50000, requireListed: false }
                }
            },
            {
                id: "apothecary", name: "Apothecary", description: "Potions, supplies, poisons, and other consumables.",
                count: { min: 6, max: 12 }, quantity: { min: 1, max: 3 },
                discovery: {
                    activeTypes: ["consumable", "loot"],
                    filterState: { consumableType: { potion: 1, poison: 1, food: 1, scroll: 1 } },
                    price: { minimum: null, maximum: 50000, requireListed: false }
                }
            },
            {
                id: "arcane-dealer", name: "Arcane Dealer", description: "Identified magical equipment and consumables.",
                count: { min: 4, max: 8 }, quantity: { min: 1, max: 1 },
                discovery: {
                    activeTypes: ["weapon", "equipment", "consumable", "container", "tool"],
                    filterState: { rarity: { mundane: -1 } },
                    price: { minimum: null, maximum: 1000000, requireListed: false }
                }
            }
        ];
    }

    static async getAvailablePacks({ packIds = [], recommendedOnly = false } = {}) {
        const available = [];
        const requested = new Set(packIds.map(String).filter(Boolean));
        const sourceConfiguration = game.settings.get("dnd5e", "packSourceConfiguration") || {};
        const packs = (game.packs?.contents || []).filter(candidate =>
            candidate.documentName === "Item"
            && (!requested.size || requested.has(candidate.collection))
            && (!recommendedOnly || this.#RECOMMENDED_PACKS.has(candidate.collection))
        );
        for (const pack of packs) {
            if (!pack.visible || sourceConfiguration[pack.collection] === false || this.#isBlockedPack(pack)) continue;
            const products = await this.#getPackCatalog(pack);
            if (!products.length) continue;
            const packageId = String(pack.metadata?.packageName || "");
            const packageType = String(pack.metadata?.packageType || (pack.collection.startsWith("world.") ? "world" : "module"));
            const official = packageType === "system" || packageId === game.system.id || pack.collection.startsWith(`${game.system.id}.`);
            const world = packageType === "world" || pack.collection.startsWith("world.");
            available.push({
                id: pack.collection,
                label: pack.metadata?.label || pack.title || pack.collection,
                originLabel: official ? game.system.title : world ? "This World" : game.modules.get(packageId)?.title || packageId || "Installed Module",
                group: official ? "official" : world ? "world" : "module",
                eligibleCount: products.length,
                recommended: this.#RECOMMENDED_PACKS.has(pack.collection)
            });
        }
        return available.sort((left, right) => left.label.localeCompare(right.label) || left.id.localeCompare(right.id));
    }
    static async getCompendiumProduct(productId) {
        const id = String(productId || "");
        if (this.#productsById.has(id)) return this.#productsById.get(id);
        const pack = (game.packs?.contents || []).find(candidate =>
            candidate.documentName === "Item" && id.startsWith(`Compendium.${candidate.collection}.Item.`)
        );
        if (!pack || this.#isBlockedPack(pack)) return null;
        await this.#getPackCatalog(pack);
        return this.#productsById.get(id) || null;
    }
    static async getAcquiredProduct(productId, { shop } = {}) {
        const record = shop?.providerData?.[DND5E_ACQUIRED_PROVIDER_ID]?.[productId];
        if (!record?.itemData) return null;
        return productFromItem(record.itemData, { id: productId, sourceUuid: record.sourceUuid || "", itemData: record.itemData });
    }

    static async getCatalog({ packIds = [], limit = 5000 } = {}) {
        const allowed = new Set(packIds.filter(Boolean));
        const packs = (game.packs?.contents || []).filter(pack =>
            pack.documentName === "Item" && !this.#isBlockedPack(pack) && (!allowed.size || allowed.has(pack.collection))
        );
        const products = [];
        for (const pack of packs) {
            for (const product of await this.#getPackCatalog(pack)) {
                products.push(product);
                if (products.length >= limit) return products.sort((a, b) => a.name.localeCompare(b.name));
            }
        }
        return products.sort((a, b) => a.name.localeCompare(b.name));
    }
    static register() {
        if (game.system?.id !== "dnd5e") return;
        ShopProductProviderRegistry.register({
            id: DND5E_COMPENDIUM_PROVIDER_ID,
            label: "D&D Compendium Items",
            merchandiseKinds: ["armor", "shield", "weapon", "ammunition"],
            cacheProducts: true,
            getProduct: productId => this.getCompendiumProduct(productId),
            getCatalog: options => this.getCatalog(options),
            getCatalogSchema: () => this.getCatalogSchema(),
            getStockTemplates: () => this.getStockTemplates(),
            getAvailablePacks: options => this.getAvailablePacks(options)
        });
        ShopProductProviderRegistry.register({
            id: DND5E_ACQUIRED_PROVIDER_ID,
            label: "Items Bought from Characters",
            getProduct: (productId, context) => this.getAcquiredProduct(productId, context)
        });
    }

    static async #getPackCatalog(pack) {
        if (!this.#packCatalogs.has(pack.collection)) {
            const promise = (async () => {
                const products = [];
                for (const entry of await this.#getPackIndex(pack)) {
                    if (!this.#isMerchantItem(entry)) continue;
                    const uuid = `Compendium.${pack.collection}.Item.${entry._id}`;
                    const source = this.#rulesSource(entry, pack);
                    const subtype = facetValue(entry.system?.type);
                    const rarity = facetValue(entry.system?.rarity, "mundane") || "mundane";
                    const merchandiseKind = this.#merchandiseKind(entry.type, subtype);
                    const properties = Array.from(entry.system?.properties || []).map(value => facetValue(value));
                    const magical = rarity !== "mundane" || properties.includes("mgc") || /(?:^|\s)\+[1-9](?:\s|$|,)/.test(entry.name);
                    const product = {
                        id: uuid,
                        providerId: DND5E_COMPENDIUM_PROVIDER_ID,
                        productId: uuid,
                        name: entry.name,
                        lotLabel: entry.name,
                        imageSrc: entry.img,
                        description: "",
                        category: entry.type,
                        typeLabel: this.#categoryLabel(entry.type),
                        subtypeLabel: this.#subtypeLabel(entry.type, subtype),
                        sourceLabel: source.label,
                        price: { amount: itemPriceInCopper(entry), currencyId: "dnd5e-cp" },
                        weight: { value: Number(entry.system?.weight?.value || 0), units: entry.system?.weight?.units || "lb" },
                        cargoSlots: 1,
                        packId: pack.collection,
                        packLabel: pack.metadata?.label || pack.title || pack.collection,
                        sourceData: { sourceUuid: uuid, itemData: null, type: entry.type, rarity, magical },
                        facets: {
                            type: entry.type,
                            weaponType: entry.type === "weapon" ? subtype : "",
                            equipmentType: entry.type === "equipment" ? subtype : "",
                            consumableType: entry.type === "consumable" ? subtype : "",
                            toolType: entry.type === "tool" ? subtype : "",
                            lootType: entry.type === "loot" ? subtype : "",
                            rarity,
                            merchandiseKind,
                            shopTags: this.#shopTags(entry.type, subtype, properties, magical),
                            availability: magical ? "special" : "standard",
                            stableKey: String(entry.system?.identifier || "") || `${entry.type}:${entry.name.toLocaleLowerCase()}`,
                            properties,
                            rulesSource: source.label
                        }
                    };
                    products.push(product);
                    this.#productsById.set(uuid, product);
                }
                return products;
            })();
            this.#packCatalogs.set(pack.collection, promise);
        }
        try {
            return await this.#packCatalogs.get(pack.collection);
        } catch (_error) {
            this.#packCatalogs.delete(pack.collection);
            return [];
        }
    }
    static async #getPackIndex(pack) {
        if (!this.#packIndexes.has(pack.collection)) this.#packIndexes.set(pack.collection, pack.getIndex({ fields: this.#INDEX_FIELDS }));
        try { return await this.#packIndexes.get(pack.collection); }
        catch (_error) { this.#packIndexes.delete(pack.collection); return []; }
    }

    static #categoryLabel(type) {
        return ({ weapon: "Weapon", equipment: "Equipment", consumable: "Consumable", container: "Container", tool: "Tool", loot: "Loot" })[type] || type;
    }

    static #subtypeLabel(type, subtype) {
        const maps = { weapon: CONFIG.DND5E?.weaponTypes, equipment: CONFIG.DND5E?.equipmentTypes, consumable: CONFIG.DND5E?.consumableTypes, tool: CONFIG.DND5E?.toolTypes, loot: CONFIG.DND5E?.lootTypes };
        const value = maps[type]?.[subtype];
        return value ? game.i18n.localize(value?.label || value) : subtype;
    }

    static #rulesSource(entry, pack) {
        const source = entry.system?.source || {};
        const packageId = String(pack.metadata?.packageName || "");
        const packageType = String(pack.metadata?.packageType || "");
        const pkg = packageType === "system" ? game.system : game.modules.get(packageId);
        const packageBooks = pkg?.flags?.dnd5e?.sourceBooks || {};
        const packageBookIds = Object.keys(packageBooks);
        const packageBook = packageBookIds.length === 1 ? packageBookIds[0] : "";
        const raw = source.custom || source.book || pack.metadata?.flags?.dnd5e?.sourceBook || packageBook;
        const label = sourceBookLabel(raw);
        return { id: label, label };
    }

    static #isBlockedPack(pack) {
        return BLOCKED_PACK_PATTERN.test(`${pack.collection} ${pack.metadata?.label || ""} ${pack.title || ""}`.toLocaleLowerCase());
    }

    static #isMerchantItem(entry) {
        if (!PHYSICAL_TYPES.has(entry?.type) || entry.system?.container) return false;
        const name = String(entry.name || "").trim().toLocaleLowerCase();
        const identifier = String(entry.system?.identifier || "").trim().toLocaleLowerCase();
        const subtype = facetValue(entry.system?.type);
        if (name.includes("unarmed strike") || identifier.includes("unarmed-strike") || identifier.includes("unarmedstrike")) return false;
        if (entry.type === "weapon" && ["natural", "monster", "unarmed"].includes(subtype)) return false;
        return entry.system?.identified !== false;
    }
}
