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

export const PF2E_COMPENDIUM_PROVIDER_ID = "augur-nexus:pf2e-compendium";
export const PF2E_ACQUIRED_PROVIDER_ID = "augur-nexus:pf2e-acquired";

const MERCHANT_TYPES = new Set(["ammo", "armor", "backpack", "book", "consumable", "equipment", "shield", "treasure", "weapon", "kit"]);
const TYPE_LABELS = {
    ammo: "Ammunition",
    armor: "Armor",
    backpack: "Backpack",
    book: "Book",
    consumable: "Consumable",
    equipment: "Equipment",
    shield: "Shield",
    treasure: "Treasure",
    weapon: "Weapon",
    kit: "Kit"
};

function Coins() {
    return game.pf2e?.Coins || null;
}

function itemPriceInCopper(item) {
    const CoinClass = 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 sourceLabelFromItem(item) {
    return String(item?.system?.publication?.title || item?.system?.source?.value || "Unspecified Source").trim();
}

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 productFromItem(item, { id = item.uuid, sourceUuid = item.uuid, itemData = null } = {}) {
    const level = Math.max(0, Number(item?.system?.level?.value) || 0);
    const rarity = String(item?.system?.traits?.rarity || "common");
    const category = String(item?.system?.category || "");
    const group = String(item?.system?.group || "");
    return {
        id,
        name: item.name,
        lotLabel: item.name,
        category: item.type,
        typeLabel: TYPE_LABELS[item.type] || item.type,
        subtypeLabel: [category, group].filter(Boolean).map(value => Pf2eItemProvider.getConfigLabel(value)).join(" · "),
        sourceLabel: sourceLabelFromItem(item),
        imageSrc: item.img,
        description: String(item.system?.description?.value || ""),
        price: { amount: itemPriceInCopper(item), currencyId: "pf2e-coins" },
        weight: { value: bulkValue(item), units: "Bulk" },
        cargoSlots: 1,
        sourceData: {
            sourceUuid,
            itemData: itemData ? foundry.utils.deepClone(itemData) : null,
            type: item.type,
            level,
            rarity,
            traits: Array.from(item.system?.traits?.value || []).map(String)
        }
    };
}

export class Pf2eItemProvider {
    static #packIndexes = new Map();
    static #packCatalogs = new Map();
    static #productsById = new Map();
    static #RECOMMENDED_PACKS = new Set(["pf2e.equipment-srd"]);
    static #INDEX_FIELDS = [
        "img", "type", "system.bulk", "system.category", "system.containerId", "system.description.value",
        "system.group", "system.identification.status", "system.level.value", "system.price", "system.publication.title", "system.range",
        "system.slug", "system.source.value", "system.temporary", "system.traits"
    ];

    static getCatalogSchema() {
        const labels = values => Object.fromEntries(Object.entries(values || {}).map(([id, value]) => [id, game.i18n.localize(value?.label || value)]));
        return {
            id: "pf2e-physical-items-v1",
            types: Array.from(MERCHANT_TYPES).map(id => ({ id, label: TYPE_LABELS[id] || id })),
            filterGroups: [
                { id: "levelBand", label: "Level", dynamic: true, optionLabels: { "0": "Level 0", "1-4": "Levels 1–4", "5-10": "Levels 5–10", "11-15": "Levels 11–15", "16+": "Levels 16+" } },
                { id: "rarity", label: "Rarity", dynamic: true, optionLabels: labels(CONFIG.PF2E?.rarityTraits) },
                { id: "itemCategory", label: "Category", types: ["weapon", "armor"], visibility: "single", dynamic: true, optionLabels: { ...labels(CONFIG.PF2E?.weaponCategories), ...labels(CONFIG.PF2E?.armorCategories) } },
                { id: "itemGroup", label: "Group", types: ["weapon", "armor"], visibility: "single", dynamic: true, optionLabels: { ...labels(CONFIG.PF2E?.weaponGroups), ...labels(CONFIG.PF2E?.armorGroups) } },
                { id: "traits", label: "Traits", dynamic: true, optionLabels: { ...labels(CONFIG.PF2E?.armorTraits), ...labels(CONFIG.PF2E?.consumableTraits), ...labels(CONFIG.PF2E?.equipmentTraits), ...labels(CONFIG.PF2E?.shieldTraits), ...labels(CONFIG.PF2E?.weaponTraits) } },
                { id: "shopTags", label: "Shop Category", dynamic: true },
                { id: "rulesSource", label: "Source", dynamic: true, placement: "bottom" }
            ]
        };
    }

    static #merchandiseKind(type) {
        return ({ armor: "armor", shield: "shield", weapon: "weapon", ammo: "ammunition" })[type] || "";
    }

    static #shopTags(type, group, traits = []) {
        const tags = new Set();
        const values = new Set(traits.map(value => String(value).toLocaleLowerCase()));
        if (type === "weapon") {
            const rangedGroups = new Set(["bow", "crossbow", "firearm", "sling"]);
            const ranged = rangedGroups.has(group);
            tags.add(ranged ? "ranged-weapon" : "melee-weapon");
        }
        if (type === "ammo") tags.add("ammunition");
        if (type === "consumable" && ["alchemical", "elixir", "potion", "poison", "healing"]
            .some(value => values.has(value))) tags.add("apothecary");
        if (["backpack", "equipment", "kit"].includes(type)) tags.add("outfitter");
        const arcaneTraits = ["magical", "scroll", "wand", "staff", "spellheart", "grimoire"];
        if (["book", "consumable", "equipment"].includes(type)
            && arcaneTraits.some(value => values.has(value))
            && !tags.has("apothecary")) tags.add("arcane-curio");
        return [...tags];
    }

    static getStockTemplates() {
        return [
            {
                id: "blacksmith", name: "Blacksmith", description: "Weapons, armor, shields, and ammunition.",
                count: { min: 8, max: 14 }, quantity: { min: 1, max: 3 },
                discovery: { activeTypes: ["weapon", "armor", "shield", "ammo"], filterState: { rarity: { common: 1 } }, price: { minimum: null, maximum: 20000, requireListed: false } }
            },
            {
                id: "general-store", name: "General Store", description: "Common adventuring gear, packs, books, kits, and trade goods.",
                count: { min: 10, max: 18 }, quantity: { min: 1, max: 5 },
                discovery: { activeTypes: ["backpack", "book", "equipment", "kit", "treasure"], filterState: { rarity: { common: 1 } }, price: { minimum: null, maximum: 5000, requireListed: false } }
            },
            {
                id: "apothecary", name: "Apothecary", description: "Alchemical goods, potions, poisons, and other consumables.",
                count: { min: 6, max: 12 }, quantity: { min: 1, max: 4 },
                discovery: { activeTypes: ["consumable"], filterState: { traits: { alchemical: 1, potion: 1, poison: 1 } }, price: { minimum: null, maximum: 20000, requireListed: false } }
            },
            {
                id: "magic-shop", name: "Magic Shop", description: "Uncommon and rare permanent items and consumables.",
                count: { min: 4, max: 8 }, quantity: { min: 1, max: 1 },
                discovery: { activeTypes: ["weapon", "armor", "shield", "equipment", "consumable", "book"], filterState: { rarity: { common: -1 } }, price: { minimum: null, maximum: 2000000, requireListed: false } }
            }
        ];
    }

    static async getAvailablePacks({ packIds = [], recommendedOnly = false } = {}) {
        const available = [];
        const requested = new Set(packIds.map(String).filter(Boolean));
        const packs = (game.packs?.contents || []).filter(candidate =>
            candidate.documentName === "Item"
            && candidate.visible
            && (!requested.size || requested.has(candidate.collection))
            && (!recommendedOnly || this.#RECOMMENDED_PACKS.has(candidate.collection))
        );
        for (const pack of packs) {
            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 || !pack.visible) return null;
        await this.#getPackCatalog(pack);
        return this.#productsById.get(id) || null;
    }
    static async getAcquiredProduct(productId, { shop } = {}) {
        const record = shop?.providerData?.[PF2E_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" && pack.visible && (!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 !== "pf2e") return;
        ShopProductProviderRegistry.register({
            id: PF2E_COMPENDIUM_PROVIDER_ID,
            label: "Pathfinder 2e 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: PF2E_ACQUIRED_PROVIDER_ID,
            label: "Items Bought from Characters",
            getProduct: (productId, context) => this.getAcquiredProduct(productId, context)
        });
    }

    static getConfigLabel(value) {
        const raw = String(value || "");
        const maps = [CONFIG.PF2E?.weaponCategories, CONFIG.PF2E?.armorCategories, CONFIG.PF2E?.weaponGroups, CONFIG.PF2E?.armorGroups];
        for (const map of maps) {
            const label = map?.[raw];
            if (label) return game.i18n.localize(label?.label || label);
        }
        return raw;
    }

    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 level = Math.max(0, Number(entry.system?.level?.value) || 0);
                    const rarity = String(entry.system?.traits?.rarity || "common");
                    const traits = Array.from(entry.system?.traits?.value || []).map(String);
                    const source = this.#rulesSource(entry, pack);
                    const product = {
                        id: uuid,
                        providerId: PF2E_COMPENDIUM_PROVIDER_ID,
                        productId: uuid,
                        name: entry.name,
                        lotLabel: entry.name,
                        imageSrc: entry.img,
                        description: String(entry.system?.description?.value || ""),
                        category: entry.type,
                        typeLabel: TYPE_LABELS[entry.type] || entry.type,
                        subtypeLabel: [entry.system?.category, entry.system?.group].filter(Boolean).map(value => this.getConfigLabel(value)).join(" · "),
                        sourceLabel: source,
                        price: { amount: itemPriceInCopper(entry), currencyId: "pf2e-coins" },
                        weight: { value: bulkValue(entry), units: "Bulk" },
                        cargoSlots: 1,
                        packId: pack.collection,
                        packLabel: pack.metadata?.label || pack.title || pack.collection,
                        sourceData: { sourceUuid: uuid, itemData: null, type: entry.type, level, rarity, traits },
                        facets: {
                            type: entry.type,
                            levelBand: this.#levelBand(level),
                            level,
                            rarity,
                            merchandiseKind: this.#merchandiseKind(entry.type),
                            shopTags: this.#shopTags(entry.type, String(entry.system?.group || ""), traits),
                            availability: rarity === "common" ? "standard" : "special",
                            stableKey: String(entry.system?.slug || "") || `${entry.type}:${entry.name.toLocaleLowerCase()}`,
                            itemCategory: String(entry.system?.category || ""),
                            itemGroup: String(entry.system?.group || ""),
                            traits,
                            rulesSource: source
                        }
                    };
                    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 #rulesSource(entry, pack) {
        return String(entry.system?.publication?.title || entry.system?.source?.value || pack.metadata?.label || pack.title || "Unspecified Source").trim();
    }

    static #levelBand(level) {
        if (level <= 0) return "0";
        if (level <= 4) return "1-4";
        if (level <= 10) return "5-10";
        if (level <= 15) return "11-15";
        return "16+";
    }

    static #isMerchantItem(entry) {
        if (!MERCHANT_TYPES.has(entry?.type)) return false;
        if (entry.type === "treasure" && (
            entry.isCoinage
            || entry.isCurrency
            || entry.system?.category === "coin"
            || entry.system?.category === "credstick"
            || entry.system?.slug === "upb"
        )) return false;
        if (entry.system?.containerId) return false;
        if (entry.system?.temporary === true) return false;
        return entry.system?.identification?.status !== "unidentified";
    }
}
