export class ShopProductProviderRegistry {
    static #providers = new Map();
    static #productPromises = new Map();
    static #catalogPromises = new Map();

    static register(definition) {
        if (!definition?.id || typeof definition.getProduct !== "function") {
            throw new Error("Shop product providers require an id and getProduct(productId, context).");
        }
        this.#clearProviderCache(definition.id);
        this.#providers.set(definition.id, definition);
        return definition;
    }

    static get(id) {
        return this.#providers.get(String(id || "")) || null;
    }

    static list() {
        return Array.from(this.#providers.values());
    }

    static listCatalogProviders() {
        return this.list().filter(provider => typeof provider.getCatalog === "function");
    }

    static getTemplates() {
        return this.list().flatMap(provider => {
            const profiles = typeof provider.getStockTemplates === "function" ? provider.getStockTemplates() : [];
            return (Array.isArray(profiles) ? profiles : []).map(profile => ({
                ...foundry.utils.deepClone(profile),
                providerId: provider.id
            }));
        });
    }

    static getCatalogSchemas() {
        return this.listCatalogProviders().flatMap(provider => {
            const schema = typeof provider.getCatalogSchema === "function" ? provider.getCatalogSchema() : null;
            return schema ? [{ ...foundry.utils.deepClone(schema), providerId: provider.id }] : [];
        });
    }

    static async resolve(providerId, productId, context = {}) {
        const provider = this.get(providerId);
        if (!provider) return null;
        const cacheKey = `${providerId}::${String(productId || "")}`;
        let product;
        if (provider.cacheProducts === true) {
            if (!this.#productPromises.has(cacheKey)) {
                this.#productPromises.set(cacheKey, Promise.resolve(provider.getProduct(productId, context)));
            }
            try {
                product = await this.#productPromises.get(cacheKey);
                if (!product) this.#productPromises.delete(cacheKey);
            } catch (error) {
                this.#productPromises.delete(cacheKey);
                throw error;
            }
        } else {
            product = await provider.getProduct(productId, context);
        }
        if (!product) return null;
        return {
            ...foundry.utils.deepClone(product),
            providerId,
            productId: String(product.id || productId)
        };
    }

    static async getCatalog(providerId, options = {}) {
        const provider = this.get(providerId);
        if (typeof provider?.getCatalog !== "function") return [];
        const packIds = Array.isArray(options.packIds) ? [...options.packIds].map(String).sort() : [];
        const cacheKey = `${providerId}::${packIds.join("|")}::${Number(options.limit) || 0}`;
        if (!this.#catalogPromises.has(cacheKey)) {
            this.#catalogPromises.set(cacheKey, Promise.resolve(provider.getCatalog(options)));
        }
        let products;
        try {
            products = await this.#catalogPromises.get(cacheKey);
        } catch (error) {
            this.#catalogPromises.delete(cacheKey);
            throw error;
        }
        if (!Array.isArray(products)) return [];
        if (provider.cacheProducts === true) {
            for (const product of products) {
                const productId = String(product?.id || product?.productId || "");
                if (!productId) continue;
                this.#productPromises.set(`${providerId}::${productId}`, Promise.resolve(product));
            }
        }
        return [...products];
    }

    static #clearProviderCache(providerId) {
        const prefix = `${String(providerId || "")}::`;
        for (const key of this.#productPromises.keys()) {
            if (key.startsWith(prefix)) this.#productPromises.delete(key);
        }
        for (const key of this.#catalogPromises.keys()) {
            if (key.startsWith(prefix)) this.#catalogPromises.delete(key);
        }
    }
}
