import { TradeGoodsProviderRegistry } from "./TradeGoodsProviderRegistry.js";

const MODULE_ID = "augur-nexus";
const SETTING_ID = "tradeGoodsWorldCatalog";
const DEFAULT_MARKET_RATES = Object.freeze({ sourceMultiplier: 0.8, cityImportMultiplier: 1.2 });
const EMPTY_WORLD_CATALOG = {
    schemaVersion: 2,
    marketRates: DEFAULT_MARKET_RATES,
    overrides: {},
    custom: []
};

export class TradeGoodsCatalog {
    static #catalogs = new Map();

    static registerSettings() {
        game.settings.register(MODULE_ID, SETTING_ID, {
            scope: "world",
            config: false,
            type: Object,
            default: foundry.utils.deepClone(EMPTY_WORLD_CATALOG),
            onChange: () => {
                this.invalidate();
                Hooks.callAll("augurNexusTradeGoodsChanged", { reason: "settings" });
            }
        });
    }

    static invalidate() { this.#catalogs.clear(); }

    static async getCatalog({ includeArchived = false } = {}) {
        const cacheKey = includeArchived ? "all" : "active";
        if (this.#catalogs.has(cacheKey)) return this.#catalogs.get(cacheKey);
        const providerCatalogs = await Promise.all(TradeGoodsProviderRegistry.getAll().map(async provider => ({
            provider,
            goods: await provider.getGoods()
        })));
        const world = this.getWorldCatalog();
        const builtIn = providerCatalogs.flatMap(({ provider, goods }) => (Array.isArray(goods) ? goods : []).map(good => {
            const catalogKey = this.#catalogKey(provider.id, good.id);
            const override = world.overrides?.[catalogKey] || {};
            return this.#normalizeGood({
                ...good,
                tradeValue: override.tradeValue ?? good.tradeValue,
                imageSrc: Object.hasOwn(override, "imageSrc") ? override.imageSrc : good.imageSrc,
                providerId: provider.id,
                providerLabel: provider.label,
                catalogKey
            }, { builtIn: true });
        }));
        const custom = (world.custom || [])
            .map(good => this.#normalizeGood(good, { builtIn: false }))
            .filter(good => includeArchived || !good.archived);
        const catalog = {
            schemaVersion: 3,
            goods: [...builtIn, ...custom]
        };
        this.#catalogs.set(cacheKey, catalog);
        return catalog;
    }

    static async getGoods(options = {}) {
        const catalog = await this.getCatalog(options);
        return catalog.goods || [];
    }

    static async getGoodsForLocation(location, options = {}) {
        const goods = await this.getGoods(options);
        const providerIds = new Set((location?.moduleData?.[MODULE_ID]?.trade?.tradeGoodsProviderIds || [])
            .map(String)
            .filter(Boolean));
        if (!providerIds.size) return goods;
        return goods.filter(good => providerIds.has(good.providerId) || good.custom === true);
    }

    static async getGood(id) {
        const goods = await this.getGoods({ includeArchived: true });
        return goods.find(good => good.catalogKey === id || good.id === id) || null;
    }

    static getWorldCatalog() {
        const source = game.settings?.get?.(MODULE_ID, SETTING_ID) || EMPTY_WORLD_CATALOG;
        return this.#normalizeWorldCatalog(source);
    }

    static getMarketRates() {
        return foundry.utils.deepClone(this.getWorldCatalog().marketRates);
    }

    static async setMarketRates({ sourceMultiplier, cityImportMultiplier } = {}) {
        const world = this.getWorldCatalog();
        world.marketRates = this.#normalizeMarketRates({ sourceMultiplier, cityImportMultiplier });
        await this.#save(world);
        return foundry.utils.deepClone(world.marketRates);
    }

    static async setPrice(goodId, tradeValue) {
        const good = await this.getGood(goodId);
        const id = good?.catalogKey || String(goodId || "");
        const value = this.#number(tradeValue, 0, 1000000, 0);
        const world = this.getWorldCatalog();
        const customIndex = world.custom.findIndex(entry => entry.catalogKey === id || entry.id === id);
        if (customIndex >= 0) world.custom[customIndex].tradeValue = value;
        else world.overrides[id] = { ...(world.overrides[id] || {}), tradeValue: value };
        await this.#save(world);
    }

    static async resetPrice(goodId) {
        const good = await this.getGood(goodId);
        const id = good?.catalogKey || String(goodId || "");
        const world = this.getWorldCatalog();
        if (!Object.hasOwn(world.overrides, id)) return;
        delete world.overrides[id].tradeValue;
        if (!Object.keys(world.overrides[id]).length) delete world.overrides[id];
        await this.#save(world);
    }

    static async resetAllPrices() {
        const world = this.getWorldCatalog();
        world.overrides = Object.fromEntries(Object.entries(world.overrides)
            .map(([id, override]) => {
                const preserved = { ...override };
                delete preserved.tradeValue;
                return [id, preserved];
            })
            .filter(([, override]) => Object.keys(override).length));
        await this.#save(world);
    }

    static async setImage(goodId, imageSrc) {
        const good = await this.getGood(goodId);
        const id = good?.catalogKey || String(goodId || "").trim();
        if (!good) throw new Error(`Trade good not found: ${id || "missing ID"}.`);
        const world = this.getWorldCatalog();
        const customIndex = world.custom.findIndex(entry => entry.catalogKey === id || entry.id === id);
        if (customIndex >= 0) world.custom[customIndex].imageSrc = String(imageSrc || "").trim();
        else world.overrides[id] = { ...(world.overrides[id] || {}), imageSrc: String(imageSrc || "").trim() };
        await this.#save(world);
        return this.getGood(id);
    }

    static async saveCustomGood(good = {}) {
        const world = this.getWorldCatalog();
        const existingId = String(good.id || "");
        const id = existingId || `custom-${this.#slug(good.name) || "trade-good"}-${foundry.utils.randomID(6).toLocaleLowerCase()}`;
        const normalized = this.#normalizeGood({
            ...good,
            id,
            providerId: "augur-nexus:custom-trade-goods",
            providerLabel: "Custom Trade Goods",
            catalogKey: this.#catalogKey("augur-nexus:custom-trade-goods", id),
            custom: true
        }, { builtIn: false });
        const index = world.custom.findIndex(entry => entry.id === id);
        if (index >= 0) world.custom[index] = normalized;
        else world.custom.push(normalized);
        await this.#save(world);
        return normalized;
    }

    static async setCustomArchived(goodId, archived) {
        const world = this.getWorldCatalog();
        const index = world.custom.findIndex(good => good.id === String(goodId || ""));
        if (index < 0) return null;
        world.custom[index].archived = !!archived;
        await this.#save(world);
        return world.custom[index];
    }

    static async #save(world) {
        await game.settings.set(MODULE_ID, SETTING_ID, this.#normalizeWorldCatalog(world));
    }

    static #normalizeWorldCatalog(source = {}) {
        return {
            schemaVersion: 2,
            marketRates: this.#normalizeMarketRates(source?.marketRates),
            overrides: Object.fromEntries(Object.entries(source?.overrides || {}).map(([id, value]) => {
                const normalized = {};
                if (Object.hasOwn(value || {}, "tradeValue")) normalized.tradeValue = this.#number(value.tradeValue, 0, 1000000, 0);
                if (Object.hasOwn(value || {}, "imageSrc")) normalized.imageSrc = String(value.imageSrc || "").trim();
                return [String(id), normalized];
            })),
            custom: (Array.isArray(source?.custom) ? source.custom : []).map(good => this.#normalizeGood(good, { builtIn: false }))
        };
    }

    static #normalizeMarketRates(source = {}) {
        return {
            sourceMultiplier: this.#number(source?.sourceMultiplier, 0, 5, DEFAULT_MARKET_RATES.sourceMultiplier),
            cityImportMultiplier: this.#number(source?.cityImportMultiplier, 1, 5, DEFAULT_MARKET_RATES.cityImportMultiplier)
        };
    }

    static #normalizeGood(source = {}, { builtIn = false } = {}) {
        const name = String(source.name || "Custom Trade Good").trim() || "Custom Trade Good";
        return {
            ...foundry.utils.deepClone(source),
            id: String(source.id || ""),
            providerId: String(source.providerId || "augur-nexus:custom-trade-goods"),
            providerLabel: String(source.providerLabel || "Custom Trade Goods"),
            catalogKey: String(source.catalogKey || this.#catalogKey(source.providerId || "augur-nexus:custom-trade-goods", source.id)),
            name,
            familyId: String(source.familyId || "custom").trim() || "custom",
            valueBand: String(source.valueBand || "custom").trim() || "custom",
            tradeValue: this.#number(source.tradeValue, 0, 1000000, 0),
            imageSrc: String(source.imageSrc === "icons/commodities/treasure/trade-goods-box.webp" ? "" : source.imageSrc || ""),
            lot: {
                label: String(source.lot?.label || `Lots of ${name}`).trim() || `Lots of ${name}`,
                description: String(source.lot?.description || "A trade shipment managed by the GM.").trim(),
                weightLb: this.#number(source.lot?.weightLb, 0, 1000000, 0)
            },
            sourceIndustryIds: Array.isArray(source.sourceIndustryIds) ? source.sourceIndustryIds.map(String) : [],
            demandCategoryIds: Array.isArray(source.demandCategoryIds) ? source.demandCategoryIds.map(String) : [],
            builtIn,
            custom: !builtIn,
            archived: !builtIn && source.archived === true
        };
    }

    static #number(value, minimum, maximum, fallback) {
        const number = Number(value);
        return Math.min(maximum, Math.max(minimum, Number.isFinite(number) ? number : fallback));
    }

    static #slug(value) {
        return String(value || "").normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLocaleLowerCase()
            .replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 36);
    }

    static #catalogKey(providerId, goodId) {
        return `${String(providerId || "unknown")}::${String(goodId || "")}`;
    }
}
