import { ShopModel } from "../models/ShopModel.js";
import { LocationEntityStore } from "../../campaign-entities/services/LocationEntityStore.js";
import { ConnectionStore } from "../../connections/services/ConnectionStore.js";
import { ConnectionTargetResolver } from "../../connections/services/ConnectionTargetResolver.js";

const MODULE_ID = "augur-nexus";

export class ShopStore {
    static #shopById = new Map();
    static #documentByShopId = new Map();
    static #shopIdByJournalEntryId = new Map();
    static #shopIdsByParentLocationId = new Map();
    static #rebuilt = false;

    static rebuildIndex() {
        this.#shopById.clear();
        this.#documentByShopId.clear();
        this.#shopIdByJournalEntryId.clear();
        this.#shopIdsByParentLocationId.clear();
        for (const document of game.journal?.contents || []) this.upsertDocument(document);
        this.#rebuilt = true;
    }

    static ensureIndex() {
        if (!this.#rebuilt) this.rebuildIndex();
    }

    static upsertDocument(document) {
        const journalEntryId = String(document?.id || "");
        if (journalEntryId) {
            const previousShopId = this.#shopIdByJournalEntryId.get(journalEntryId);
            if (previousShopId) this.#removeShopId(previousShopId);
        }
        const shop = ShopModel.fromDocument(document);
        if (!shop?.id) return null;
        this.#removeShopId(shop.id);
        this.#shopById.set(shop.id, shop);
        this.#documentByShopId.set(shop.id, document);
        if (shop.journalEntryId) this.#shopIdByJournalEntryId.set(shop.journalEntryId, shop.id);
        const parentLocationId = String(shop.hierarchy?.parentLocationId || "");
        if (parentLocationId) {
            const shopIds = this.#shopIdsByParentLocationId.get(parentLocationId) || new Set();
            shopIds.add(shop.id);
            this.#shopIdsByParentLocationId.set(parentLocationId, shopIds);
        }
        return this.#clone(shop);
    }

    static removeDocument(documentOrId) {
        const value = typeof documentOrId === "object"
            ? String(documentOrId?.id || "")
            : String(documentOrId || "").trim();
        const shopId = this.#shopById.has(value)
            ? value
            : this.#shopIdByJournalEntryId.get(this.#journalEntryId(value));
        if (!shopId) return null;
        const shop = this.#shopById.get(shopId);
        this.#removeShopId(shopId);
        return shop ? this.#clone(shop) : null;
    }

    static getShop(idOrUuid) {
        this.ensureIndex();
        const value = String(idOrUuid || "").trim();
        if (!value) return null;
        const direct = this.#shopById.get(value);
        if (direct) return this.#clone(direct);
        const shopId = this.#shopIdByJournalEntryId.get(this.#journalEntryId(value));
        if (shopId) return this.#clone(this.#shopById.get(shopId));
        const shop = [...this.#shopById.values()].find(candidate => candidate.uuid === value);
        return shop ? this.#clone(shop) : null;
    }

    static getShops() {
        this.ensureIndex();
        return [...this.#shopById.values()]
            .sort((left, right) => left.name.localeCompare(right.name))
            .map(shop => this.#clone(shop));
    }

    static getShopsForParentLocation(locationId) {
        this.ensureIndex();
        const shopIds = this.#shopIdsByParentLocationId.get(String(locationId || ""));
        if (!shopIds) return [];
        return [...shopIds]
            .map(shopId => this.#shopById.get(shopId))
            .filter(Boolean)
            .sort((left, right) => left.name.localeCompare(right.name))
            .map(shop => this.#clone(shop));
    }

    static resolveDocument(idOrUuid) {
        this.ensureIndex();
        const value = String(idOrUuid || "").trim();
        if (!value) return null;
        if (this.#documentByShopId.has(value)) return this.#documentByShopId.get(value);
        const shopId = this.#shopIdByJournalEntryId.get(this.#journalEntryId(value));
        if (shopId) return this.#documentByShopId.get(shopId) || null;
        const shop = [...this.#shopById.values()].find(candidate => candidate.uuid === value);
        return shop ? this.#documentByShopId.get(shop.id) || null : null;
    }

    static getJournalPage(idOrUuid) {
        const document = this.resolveDocument(idOrUuid);
        return document?.pages?.contents?.find(candidate => candidate.type === "text") || null;
    }

    static async getOrCreateJournalPage(idOrUuid) {
        const document = this.resolveDocument(idOrUuid);
        if (!document) return null;
        const existing = this.getJournalPage(idOrUuid);
        if (existing) return existing;
        return this.#createPage(document);
    }

    static async createShop(data = {}) {
        const shop = ShopModel.normalize(data);
        const folder = await this.#getOrCreateFolder();
        const entry = await JournalEntry.create({
            name: shop.name,
            folder: folder?.id || null,
            ownership: { default: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER },
            flags: { [MODULE_ID]: { shop } },
            pages: [this.#pageData()]
        });
        try {
            const created = this.upsertDocument(entry);
            Hooks.callAll("augurNexusShopsChanged", { reason: "create", shop: created, document: entry });
            return created;
        } catch (error) {
            await this.#removeConnections(entry);
            await entry.delete().catch(cleanupError =>
                console.error("Augur: Nexus | Failed to clean up a partially created Shop.", cleanupError, entry)
            );
            throw error;
        }
    }

    static async updateShop(idOrUuid, patch = {}, { nameIntent = "manual" } = {}) {
        const document = this.resolveDocument(idOrUuid);
        if (!document) throw new Error("Shop could not be found.");
        const current = ShopModel.fromDocument(document);
        const merged = foundry.utils.mergeObject(current, patch, { inplace: false, recursive: true });
        if (Object.hasOwn(patch, "inventory")) merged.inventory = foundry.utils.deepClone(patch.inventory);
        if (Object.hasOwn(patch, "providerData")) merged.providerData = foundry.utils.deepClone(patch.providerData);
        if (Object.hasOwn(patch, "restockProfile")) merged.restockProfile = foundry.utils.deepClone(patch.restockProfile);
        let shop = ShopModel.normalize(merged);
        if (shop.name !== current.name && nameIntent !== "derived") {
            shop = ShopModel.normalize({
                ...shop,
                naming: { ...shop.naming, mode: "custom" }
            });
        }
        await document.update({ name: shop.name, [`flags.${MODULE_ID}.shop`]: shop });
        const updated = this.upsertDocument(document);
        const { NexusMarkerService } = await import("../../markers/services/NexusMarkerService.js");
        await NexusMarkerService.syncMarkersForTarget({ kind: "nexus-shop", shopId: updated.id });
        Hooks.callAll("augurNexusShopsChanged", { reason: "update", shop: updated, document });
        return updated;
    }

    static async deleteShop(idOrUuid) {
        const document = this.resolveDocument(idOrUuid);
        if (!document) return false;
        const shop = ShopModel.fromDocument(document);
        await this.#removeConnections(document);
        const { NexusMarkerService } = await import("../../markers/services/NexusMarkerService.js");
        await NexusMarkerService.deleteMarkersForTarget({ kind: "nexus-shop", shopId: shop.id });
        await document.delete();
        this.removeDocument(shop.id);
        Hooks.callAll("augurNexusShopsChanged", { reason: "delete", shop, document: null });
        return true;
    }

    static getParentLocation(shopOrId) {
        const shop = typeof shopOrId === "string" ? this.getShop(shopOrId) : shopOrId;
        const locationId = String(shop?.hierarchy?.parentLocationId || "");
        if (!locationId) return null;
        return LocationEntityStore.getLocation(locationId);
    }

    static getParentLabel(shopOrId) {
        return this.getParentLocation(shopOrId)?.display?.name || "Independent Shop";
    }

    static async #createPage(entry) {
        const [page] = await entry.createEmbeddedDocuments("JournalEntryPage", [this.#pageData()]);
        return page || null;
    }

    static #pageData() {
        return {
            name: "Shop Journal",
            type: "text",
            sort: CONST.SORT_INTEGER_DENSITY,
            text: { content: "", format: 1 }
        };
    }

    static async #removeConnections(document) {
        const target = ConnectionTargetResolver.fromDocument(document, { category: "service" });
        if (!target) return false;
        return ConnectionStore.removeConnectionsForTarget(target);
    }

    static async #getOrCreateFolder() {
        let parent = game.folders.find(folder =>
            folder.name === "Augur Campaign Entities" && folder.type === "JournalEntry" && !folder.folder
        );
        if (!parent) parent = await Folder.create({ name: "Augur Campaign Entities", type: "JournalEntry", color: "#1c3246" });
        let folder = game.folders.find(candidate =>
            candidate.name === "Shops" && candidate.type === "JournalEntry" && candidate.folder?.id === parent.id
        );
        if (!folder) folder = await Folder.create({ name: "Shops", type: "JournalEntry", color: "#8a5a24", folder: parent.id });
        return folder;
    }
    static #removeShopId(shopId) {
        const shop = this.#shopById.get(shopId);
        if (!shop) return;
        if (shop.journalEntryId) this.#shopIdByJournalEntryId.delete(shop.journalEntryId);
        const parentLocationId = String(shop.hierarchy?.parentLocationId || "");
        if (parentLocationId) {
            const shopIds = this.#shopIdsByParentLocationId.get(parentLocationId);
            shopIds?.delete(shopId);
            if (!shopIds?.size) this.#shopIdsByParentLocationId.delete(parentLocationId);
        }
        this.#shopById.delete(shopId);
        this.#documentByShopId.delete(shopId);
    }

    static #journalEntryId(value) {
        const match = String(value || "").match(/^JournalEntry\.([^.]+)$/);
        return match?.[1] || String(value || "");
    }

    static #clone(shop) {
        return shop ? foundry.utils.deepClone(shop) : null;
    }
}
