import { ShopModel } from "../models/ShopModel.js";
import { ShopRestockProfileModel } from "../models/ShopRestockProfileModel.js";
import { ShopCatalogFilterService } from "./ShopCatalogFilterService.js";
import { ShopIconCatalogRegistry } from "./ShopIconCatalogRegistry.js";
import { ShopPresetRegistry } from "./ShopPresetRegistry.js";
import { ShopProductProviderRegistry } from "./ShopProductProviderRegistry.js";
import { ShopRestockService } from "./ShopRestockService.js";

export class ShopPresetDraftService {
    static async createCandidate({ presetId = "", name = "", seed = foundry.utils.randomID() } = {}) {
        const preset = ShopPresetRegistry.list().find(entry => entry.id === String(presetId || ""));
        if (!preset) throw new Error(`Shop preset is unavailable for this game system: ${presetId}`);
        const draft = ShopModel.normalize({
            name: String(name || preset.defaultName || preset.name),
            source: { moduleId: preset.moduleId, templateId: preset.id, seed }
        });
        const recommendedOnly = preset.defaults?.sourceMode !== "all";
        const { catalog, packOptions } = await this.loadCatalog(draft.restockProfile, { recommendedOnly });
        const candidate = await this.applyPreset(draft, preset, {
            catalog,
            packOptions,
            initializeGeneration: true,
            name: String(name || preset.defaultName || preset.name)
        });
        const result = await ShopRestockService.generate(candidate.restockProfile, {
            shop: candidate,
            seed,
            warn: false
        });
        candidate.inventory = result.inventory;
        return ShopModel.normalize(candidate);
    }

    static async loadCatalog(profile = {}, { recommendedOnly = false } = {}) {
        const providers = ShopProductProviderRegistry.listCatalogProviders();
        const providerScopes = profile.discovery?.providerScopes || {};
        const packSets = await Promise.all(providers.map(async provider => {
            let packs = typeof provider.getAvailablePacks === "function"
                ? await provider.getAvailablePacks({ recommendedOnly })
                : [];
            if (recommendedOnly && !packs.length && typeof provider.getAvailablePacks === "function") {
                packs = await provider.getAvailablePacks();
            }
            if (!Object.hasOwn(providerScopes, provider.id)) providerScopes[provider.id] = packs.map(pack => pack.id);
            return packs.map(pack => ({ ...pack, providerId: provider.id }));
        }));
        const catalogs = await Promise.all(providers.map(provider => {
            const packIds = providerScopes[provider.id] || [];
            return packIds.length
                ? ShopProductProviderRegistry.getCatalog(provider.id, { packIds, limit: 5000 })
                : [];
        }));
        return {
            catalog: catalogs.flat().sort((left, right) =>
                left.name.localeCompare(right.name)
                || String(left.sourceLabel || "").localeCompare(String(right.sourceLabel || ""))
            ),
            packOptions: packSets.flat()
        };
    }

    static async applyPreset(draft, preset, {
        catalog = [],
        packOptions = [],
        initializeGeneration = false,
        name = ""
    } = {}) {
        const next = ShopModel.normalize(draft);
        const settings = { ...preset.defaults };
        settings.size = ["modest", "standard", "extensive"].includes(settings.size) ? settings.size : "standard";
        settings.sourceMode = settings.sourceMode === "all" ? "all" : "recommended";
        settings.includeSpecial = settings.includeSpecial === true;
        settings.maximumLevel = settings.maximumLevel === "" || settings.maximumLevel === null || settings.maximumLevel === undefined
            ? null
            : Math.max(0, Math.floor(Number(settings.maximumLevel) || 0));
        const size = preset.sizes?.[settings.size]
            || Object.values(preset.sizes || {})[0]
            || { count: 12, quantity: { min: 1, max: 3 } };
        const recipe = preset.catalogRules?.systems?.[game.system?.id]
            || preset.catalogRules?.default
            || { merchandiseKinds: preset.merchandiseKinds || [], includeTags: [], excludeTags: [], activeTypes: [], filterState: {} };
        const recommendedPacks = new Set(packOptions.filter(pack => pack.recommended).map(pack => pack.id));
        const sourceLabels = [...new Set(catalog
            .filter(product => recommendedPacks.has(product.packId))
            .map(product => String(product.facets?.rulesSource || ""))
            .filter(Boolean))];
        const filterState = {
            ...foundry.utils.deepClone(recipe.filterState || {}),
            availability: settings.includeSpecial ? {} : { standard: 1 }
        };
        if (recipe.merchandiseKinds?.length) {
            filterState.merchandiseKind = Object.fromEntries(recipe.merchandiseKinds.map(kind => [kind, 1]));
        }
        if (recipe.includeTags?.length || recipe.excludeTags?.length) {
            filterState.shopTags = {
                ...Object.fromEntries((recipe.includeTags || []).map(tag => [tag, 1])),
                ...Object.fromEntries((recipe.excludeTags || []).map(tag => [tag, -1]))
            };
        }
        if (settings.sourceMode === "recommended" && sourceLabels.length) {
            filterState.rulesSource = Object.fromEntries(sourceLabels.map(source => [source, 1]));
        }
        if (settings.maximumLevel !== null && catalog.some(product => product.facets?.levelBand)) {
            const bands = [["0", 0], ["1-4", 1], ["5-10", 5], ["11-15", 11], ["16+", 16]];
            filterState.levelBand = Object.fromEntries(
                bands.filter(([, lower]) => lower <= settings.maximumLevel).map(([band]) => [band, 1])
            );
        }
        const recipeProducts = catalog.filter(product => {
            const tags = new Set((product.facets?.shopTags || []).map(String));
            if (recipe.merchandiseKinds?.length
                && !recipe.merchandiseKinds.includes(String(product.facets?.merchandiseKind || ""))) return false;
            if (recipe.includeTags?.length && !recipe.includeTags.some(tag => tags.has(tag))) return false;
            if (recipe.excludeTags?.some(tag => tags.has(tag))) return false;
            if (recipe.activeTypes?.length
                && !recipe.activeTypes.includes(String(product.facets?.type || product.category || ""))) return false;
            if (settings.sourceMode === "recommended" && recommendedPacks.size && !recommendedPacks.has(product.packId)) return false;
            if (!settings.includeSpecial && String(product.facets?.availability || "standard").toLocaleLowerCase() !== "standard") return false;
            if (settings.maximumLevel !== null && Number.isFinite(Number(product.facets?.level))
                && Number(product.facets.level) > settings.maximumLevel) return false;
            return Number(product.price?.amount || 0) > 0;
        });
        const types = recipe.activeTypes?.length ? recipe.activeTypes : [...new Set(recipeProducts
            .map(product => String(product.facets?.type || product.category || ""))
            .filter(Boolean))];
        const discovery = ShopCatalogFilterService.normalizeState({
            ...next.restockProfile.discovery,
            activeTypes: types,
            filterState,
            price: { minimum: null, maximum: null, requireListed: false }
        });
        const matches = ShopCatalogFilterService.apply(recipeProducts, discovery);
        const unique = new Map();
        for (const product of matches) {
            const stableKey = String(product.facets?.stableKey || product.name).trim().toLocaleLowerCase();
            const key = `${product.facets?.merchandiseKind || product.category}:${stableKey}`;
            if (!unique.has(key)) unique.set(key, product);
        }
        const generation = initializeGeneration
            ? {
                countMin: size.count,
                countMax: size.count,
                quantityMin: size.quantity.min,
                quantityMax: size.quantity.max,
                preserveCurated: true
            }
            : next.restockProfile.generation;
        next.restockProfile = ShopRestockProfileModel.normalize({
            entries: [...unique.values()].map(product => ({
                providerId: product.providerId,
                productId: product.productId,
                enabled: true,
                weight: 1,
                quantity: { min: 0, max: 0 },
                priceOverride: null,
                labelHint: product.name,
                sourceHint: product.packId || product.sourceLabel || ""
            })),
            discovery,
            generation,
            preset: {
                id: preset.id,
                moduleId: preset.moduleId,
                size: settings.size,
                sourceMode: settings.sourceMode,
                includeSpecial: settings.includeSpecial,
                customized: false,
                maximumLevel: settings.maximumLevel
            },
            revision: next.restockProfile.revision + 1
        });
        const imageSelection = ShopModel.getImageSelection(next);
        if ((!ShopModel.getImage(next) || imageSelection.mode === "preset") && preset.defaultImageId) {
            const defaultIcon = await ShopIconCatalogRegistry.getIcon(preset.defaultImageId);
            if (defaultIcon) {
                next.display.imageSrc = defaultIcon.imageSrc;
                next.display.imageSelection = { mode: "preset", id: defaultIcon.id };
            }
        }
        if (name) next.name = String(name);
        return ShopModel.normalize(next);
    }
}
