import { getShop, getShopNamingState, openShopIconPicker, restoreShopDerivedName, updateShop } from "/modules/augur-nexus/scripts/api/shops.js";
import { getLocation, updateLocation } from "/modules/augur-nexus/scripts/api/locations.js";
import { SettlementMarketplaceInventoryService } from "../services/SettlementMarketplaceInventoryService.js";
import { MAX_MARKETPLACE_GOODS_PER_ROLE, SettlementMarketplaceModel } from "../models/SettlementMarketplaceModel.js";
import { TradeGoodsCatalog } from "../services/TradeGoodsCatalog.js";
import { TradeBasinEconomyService } from "../services/TradeBasinEconomyService.js";
import { TradeGoodsCatalogApplication } from "./TradeGoodsCatalogApplication.js";

const { ApplicationV2, HandlebarsApplicationMixin, DialogV2 } = foundry.applications.api;
const MODULE_ID = "augur-nexus";

export class SettlementMarketplaceEditor extends HandlebarsApplicationMixin(ApplicationV2) {
    #shopId = "";
    #shop = null;
    #location = null;
    #goods = [];
    #produced = new Set();
    #rules = null;
    #inventory = [];
    #display = null;
    #name = "";
    #treasury = null;
    #tab = "economy";
    #query = "";
    #dirty = false;
    #invalidStockCount = 0;
    #stockNeedsRebuild = false;
    #onSaved = null;
    #onReady = null;
    #didSignalReady = false;
    #tradeGoodsChangedHook = null;

    static DEFAULT_OPTIONS = {
        id: "augur-nexus-marketplace-editor",
        classes: ["augur-nexus", "nexus-shop-editor-window", "fantasy-marketplace-editor-window"],
        tag: "div",
        window: { title: "Marketplace Editor", resizable: true, minimizable: true },
        position: { width: 1180, height: 780 },
        actions: {
            selectTab: SettlementMarketplaceEditor._onSelectTab,
            setGoodState: SettlementMarketplaceEditor._onSetGoodState,
            rebuildStock: SettlementMarketplaceEditor._onRebuildStock,
            restoreGeneratedName: SettlementMarketplaceEditor._onRestoreGeneratedName,
            changeIcon: SettlementMarketplaceEditor._onChangeIcon,
            manageTradeGoods: SettlementMarketplaceEditor._onManageTradeGoods,
            save: SettlementMarketplaceEditor._onSave,
            cancel: SettlementMarketplaceEditor._onCancel
        }
    };

    static PARTS = {
        content: { template: "modules/augur-nexus/templates/trade/settlement-marketplace-editor.hbs" }
    };

    static show({ shopId = "", onSaved = null, onReady = null } = {}) {
        if (!game.user?.isGM || !shopId) return null;
        const app = new this({ shopId, onSaved, onReady });
        app.render(true, { focus: true });
        return app;
    }

    constructor({ shopId = "", onSaved = null, onReady = null } = {}, options = {}) {
        super(options);
        this.#shopId = String(shopId || "");
        this.#onSaved = typeof onSaved === "function" ? onSaved : null;
        this.#onReady = typeof onReady === "function" ? onReady : null;
        this.#tradeGoodsChangedHook = () => void this.#reloadGoods();
        Hooks.on("augurNexusTradeGoodsChanged", this.#tradeGoodsChangedHook);
    }

    get title() { return this.#name ? `Edit Marketplace — ${this.#name}` : "Marketplace Editor"; }

    async _prepareContext() {
        if (!this.#shop) await this.#initialize();
        if (!this.#shop || !this.#location) return { missing: true };
        const produced = this.#produced;
        const goods = this.#goods.map(good => ({
            ...good,
            produced: produced.has(good.id),
            neutral: !produced.has(good.id),
            producedDisabled: produced.size >= MAX_MARKETPLACE_GOODS_PER_ROLE && !produced.has(good.id),
            valueLabel: `${good.tradeValue} gp / lot`,
            familyLabel: String(good.familyId || "Trade good").replace(/-/g, " "),
            searchText: `${good.name} ${good.familyId || ""} ${good.valueBand || ""} ${good.lot?.label || ""}`.toLocaleLowerCase()
        }));
        const goodsById = new Map(this.#goods.map(good => [good.id, good]));
        const basinGoods = TradeBasinEconomyService.getMarketState(this.#location).basin?.goods || new Set();
        const marketRates = TradeGoodsCatalog.getMarketRates();
        const naming = getShopNamingState(this.#shop);
        const followsGeneratedName = naming?.isDerived === true && this.#name === naming.generatedName;
        const stock = this.#inventory.map(row => {
            const good = goodsById.get(row.productId);
            if (!good) return null;
            return {
                ...row,
                name: good.name,
                imageSrc: good.imageSrc,
                valueLabel: `${good.tradeValue} gp`,
                produced: produced.has(good.id),
                supplied: !produced.has(good.id) && basinGoods.has(good.id),
                searchText: `${good.name} ${good.familyId || ""} ${good.valueBand || ""}`.toLocaleLowerCase()
            };
        }).filter(Boolean);
        return {
            missing: false,
            isEconomyTab: this.#tab === "economy",
            isStockTab: this.#tab === "stock",
            dirty: this.#dirty,
            query: this.#query,
            locationName: this.#location.display?.name || "Settlement",
            name: this.#name,
            naming: {
                isDerived: followsGeneratedName,
                isCustom: !followsGeneratedName && naming?.canRestore === true,
                canRestore: naming?.canRestore === true && !followsGeneratedName,
                generatedName: naming?.generatedName || ""
            },
            imageSrc: this.#display.imageSrc,
            goods,
            producedCount: produced.size,
            goodsRoleLimit: MAX_MARKETPLACE_GOODS_PER_ROLE,
            stock,
            stockCount: stock.length,
            invalidStockCount: this.#invalidStockCount,
            stockNeedsRebuild: this.#stockNeedsRebuild,
            rules: {
                regionalPercent: Math.round(this.#rules.regionalMultiplier * 100),
                cityImportPercent: Math.round(marketRates.cityImportMultiplier * 100),
                buysTradeGoods: this.#rules.buysTradeGoods,
                regionalVariety: this.#rules.regionalVariety,
                quantityMin: this.#rules.quantityMin,
                quantityMax: this.#rules.quantityMax
            },
            treasuryGp: Number((this.#treasury.amount / 100).toFixed(2)),
            unlimitedTreasury: this.#treasury.unlimited
        };
    }

    _onRender(context, options) {
        super._onRender(context, options);
        if (this.window?.title) this.window.title.textContent = this.title;
        if (!this.#didSignalReady) {
            this.#didSignalReady = true;
            this.#onReady?.(this);
        }
        for (const input of this.element.querySelectorAll("[data-market-setting]")) {
            const eventName = input.matches('input[type="checkbox"]') ? "change" : "input";
            input.addEventListener(eventName, () => {
                this.#captureFields();
                if (["regionalVariety", "quantityMin", "quantityMax"].includes(input.name)) this.#stockNeedsRebuild = true;
                this.#dirty = true;
            });
        }
        const search = this.element.querySelector('[name="marketGoodsSearch"]');
        search?.addEventListener("input", event => {
            this.#query = String(event.currentTarget.value || "");
            this.#applySearch();
        });
        this.#applySearch();
    }

    static _onSelectTab(_event, target) {
        this.#captureFields();
        this.#tab = target.dataset.tab === "stock" ? "stock" : "economy";
        this.render({ parts: ["content"] });
    }

    static _onSetGoodState(_event, target) {
        const id = String(target.dataset.goodId || "");
        const state = String(target.dataset.state || "neutral");
        if (!id) return;
        if (state === "produced" && !this.#produced.has(id) && this.#produced.size >= MAX_MARKETPLACE_GOODS_PER_ROLE) {
            return ui.notifications.warn(`A settlement can produce up to ${MAX_MARKETPLACE_GOODS_PER_ROLE} trade goods.`);
        }
        this.#produced.delete(id);
        if (state === "produced") this.#produced.add(id);
        this.#stockNeedsRebuild = true;
        this.#dirty = true;
        this.render({ parts: ["content"] });
    }

    static async _onRebuildStock() {
        this.#captureFields();
        const confirmed = await DialogV2.confirm({
            window: { title: "Rebuild Marketplace Stock?" },
            content: "<p>This rebuilds stock from local exports and goods available through this trade network. Generated quantities will be replaced; manually curated trade goods will be preserved.</p>",
            yes: { label: "Rebuild Stock", icon: "fas fa-arrows-rotate" },
            no: { label: "Cancel" },
            rejectClose: false
        });
        if (!confirmed) return;
        await this.#rebuildInventory();
        this.#dirty = true;
        this.#tab = "stock";
        this.render({ parts: ["content"] });
    }

    static async _onRestoreGeneratedName() {
        const restored = await restoreShopDerivedName(this.#shopId);
        if (!restored) return ui.notifications.warn("This Marketplace does not have a generated settlement name to restore.");
        this.#shop = restored;
        this.#name = restored.name;
        ui.notifications.info(`${restored.name} now follows the settlement name.`);
        this.render({ parts: ["content"] });
    }

    static async _onChangeIcon() {
        await openShopIconPicker({
            currentImageSrc: this.#display.imageSrc,
            onSelect: selection => {
                this.#display.imageSrc = String(selection?.src || "");
                this.#display.imageSelection = {
                    mode: ["preset", "catalog", "custom"].includes(selection?.mode) ? selection.mode : "custom",
                    id: String(selection?.id || "")
                };
                this.#dirty = true;
                this.render({ parts: ["content"] });
            }
        });
    }

    static _onManageTradeGoods() { return TradeGoodsCatalogApplication.show(); }

    static async _onSave() { await this.#save(); }
    static async _onCancel() { await this.close(); }

    async close(options = {}) {
        if (this.#dirty && !options.force) {
            const discard = await DialogV2.confirm({
                window: { title: "Discard Marketplace Changes?" },
                content: "<p>Close the Marketplace Editor and discard all unsaved changes?</p>",
                yes: { label: "Discard", icon: "fas fa-trash" },
                no: { label: "Keep Editing" },
                rejectClose: false
            });
            if (!discard) return this;
        }
        if (this.#tradeGoodsChangedHook) Hooks.off("augurNexusTradeGoodsChanged", this.#tradeGoodsChangedHook);
        return super.close(options);
    }

    async #initialize() {
        this.#shop = getShop(this.#shopId);
        if (!SettlementMarketplaceModel.isMarketplace(this.#shop)) return;
        this.#location = getLocation(this.#shop.hierarchy?.parentLocationId);
        if (!this.#location) return;
        this.#goods = await TradeGoodsCatalog.getGoodsForLocation(this.#location);
        const economy = SettlementMarketplaceModel.getEconomy(this.#location);
        this.#produced = new Set(economy.produces.map(entry => entry.goodId));
        this.#rules = SettlementMarketplaceModel.getRules(this.#shop, this.#location);
        this.#name = this.#shop.name;
        this.#display = foundry.utils.deepClone(this.#shop.display || {});
        this.#treasury = foundry.utils.deepClone(this.#shop.treasury || { amount: 50000, unlimited: false });
        const knownIds = new Set(this.#goods.map(good => good.id));
        this.#invalidStockCount = this.#shop.inventory.filter(row => !knownIds.has(row.productId)).length;
        this.#inventory = foundry.utils.deepClone(this.#shop.inventory.filter(row => knownIds.has(row.productId)));
        const generatedStockIds = new Set(this.#inventory.filter(row => row.generated !== false).map(row => row.productId));
        const expected = await SettlementMarketplaceInventoryService.create({
            entity: this.#location,
            shop: this.#shop,
            rules: this.#rules,
            seed: this.#shop.source?.seed || this.#shop.id,
            providerId: ""
        });
        const expectedStockIds = new Set(expected.map(row => row.productId));
        this.#stockNeedsRebuild = generatedStockIds.size !== expectedStockIds.size
            || [...expectedStockIds].some(goodId => !generatedStockIds.has(goodId));
    }

    async #reloadGoods() {
        if (this.rendered === false) return;
        this.#captureFields();
        this.#goods = await TradeGoodsCatalog.getGoodsForLocation(this.#location);
        const worldRules = SettlementMarketplaceModel.getRules(this.#shop, this.#location);
        this.#rules.regionalMultiplier = worldRules.regionalMultiplier;
        this.#rules.importMultiplier = worldRules.importMultiplier;
        this.render({ parts: ["content"] });
    }

    #captureFields() {
        if (!this.element || !this.#rules) return;
        const value = name => this.element.querySelector(`[name="${name}"]`)?.value;
        const checked = name => this.element.querySelector(`[name="${name}"]`)?.checked;
        if (value("marketName") !== undefined) this.#name = String(value("marketName") || "Marketplace").trim() || "Marketplace";
        if (checked("buysTradeGoods") !== undefined) this.#rules.buysTradeGoods = checked("buysTradeGoods");
        this.#rules.regionalVariety = Math.max(0, Math.min(10, Math.floor(Number(value("regionalVariety") ?? this.#rules.regionalVariety) || 0)));
        const quantityMin = Math.max(1, Math.floor(Number(value("quantityMin") ?? this.#rules.quantityMin) || 1));
        this.#rules.quantityMin = quantityMin;
        this.#rules.quantityMax = Math.max(quantityMin, Math.min(99, Math.floor(Number(value("quantityMax") ?? this.#rules.quantityMax) || quantityMin)));
        if (value("treasuryGp") !== undefined) this.#treasury.amount = Math.max(0, Math.round((Number(value("treasuryGp")) || 0) * 100));
        if (checked("unlimitedTreasury") !== undefined) this.#treasury.unlimited = checked("unlimitedTreasury");
    }

    async #save() {
        this.#captureFields();
        const economy = SettlementMarketplaceModel.toEconomy([...this.#produced]);
        if (this.#stockNeedsRebuild) await this.#rebuildInventory();
        this.#location = await updateLocation(this.#location.id, { economy });
        const marketplace = SettlementMarketplaceModel.toModuleData(this.#rules);
        const saved = await updateShop(this.#shop.id, {
            name: this.#name,
            display: this.#display,
            inventory: this.#inventory,
            treasury: this.#treasury,
            configuration: {
                finiteStock: true,
                buyingEnabled: true,
                sellingEnabled: true,
                customerPurchaseMultiplier: 1,
                customerSaleMultiplier: 1,
                tradePolicyId: this.#shop.configuration?.tradePolicyId || ""
            },
            moduleData: {
                ...(this.#shop.moduleData || {}),
                [MODULE_ID]: { ...(this.#shop.moduleData?.[MODULE_ID] || {}), marketplace }
            }
        });
        this.#dirty = false;
        await this.#onSaved?.(saved);
        ui.notifications.info(`${saved.name} and the ${this.#location.display?.name || "settlement"} economy were updated.`);
        await this.close({ force: true });
    }

    async #rebuildInventory() {
        const economy = SettlementMarketplaceModel.toEconomy([...this.#produced]);
        const curated = this.#inventory.filter(row => row.generated === false);
        const generated = await SettlementMarketplaceInventoryService.create({
            entity: this.#location,
            shop: this.#shop,
            economy,
            rules: this.#rules,
            seed: foundry.utils.randomID(),
            providerId: ""
        });
        this.#inventory = [...curated, ...generated];
        this.#invalidStockCount = 0;
        this.#stockNeedsRebuild = false;
    }

    #applySearch() {
        const needle = this.#query.trim().toLocaleLowerCase();
        for (const card of this.element?.querySelectorAll?.("[data-market-good]") || []) {
            card.hidden = !!needle && !String(card.dataset.searchText || "").includes(needle);
        }
    }
}
