import { LocationEntityModel } from "../../campaign-entities/models/LocationEntityModel.js";
import { RegionProviderRegistry } from "./RegionProviderRegistry.js";
import { RegionPopulationRunStore } from "./RegionPopulationRunStore.js";
import { RegionChronicleService } from "./RegionChronicleService.js";

export class RegionPopulationProjectionCleanup {
    static #pending = Promise.resolve();

    static registerHooks() {
        Hooks.on("augurNexusCampaignEntitiesChanged", payload => {
            if (payload?.reason === "deleteLocation") this.#enqueue(payload?.entity?.id);
            if (payload?.reason === "deleteNpc") this.#enqueueEntity("npc", payload?.entity?.id);
            if (payload?.reason === "deleteFaction") this.#enqueueEntity("faction", payload?.entity?.id);
        });
        Hooks.on("deleteJournalEntry", (document, options = {}) => {
            if (options?.["augur-nexus"]?.managedCascade) return;
            if (RegionChronicleService.isChronicle(document)) {
                this.#enqueueChronicle(document);
                return;
            }
            this.#enqueue(LocationEntityModel.fromDocument(document)?.id);
        });
        Hooks.on("updateScene", (scene, changes) => {
            if (!Object.hasOwn(changes || {}, "name")) return;
            this.#enqueueChronicleRename(scene, changes.name);
        });
    }

    static async reconcileManagedDeletion({ entities = [], locationIds = [] } = {}) {
        const idsByField = {
            npcIds: new Set(),
            factionIds: new Set(),
            locationIds: new Set((locationIds || []).map(String).filter(Boolean))
        };
        for (const entity of entities || []) {
            const field = { npc: "npcIds", faction: "factionIds" }[entity?.entityType];
            const id = String(entity?.id || "").trim();
            if (field && id) idsByField[field].add(id);
        }

        if (!Object.values(idsByField).some(ids => ids.size)) return;
        for (const scene of game.scenes?.contents || []) {
            const run = RegionPopulationRunStore.get(scene);
            if (!run) continue;
            const provider = RegionProviderRegistry.get(run.regionProviderId);

            for (const locationId of idsByField.locationIds) {
                if (typeof provider?.removeCellOverlaysForOwner !== "function") break;
                for (const manifest of run.runs?.length ? run.runs : [run]) {
                    if (!manifest.locationIds?.includes(locationId) || !manifest.overlayContributionId) continue;
                    await provider.removeCellOverlaysForOwner(scene, manifest.overlayContributionId, locationId);
                }
            }

            let changed = false;
            for (const [field, deletedIds] of Object.entries(idsByField)) {
                if (!deletedIds.size) continue;
                const current = run[field] || [];
                const next = current.filter(id => !deletedIds.has(id));
                if (next.length !== current.length) {
                    run[field] = next;
                    changed = true;
                }
                run.runs = (run.runs || []).map(manifest => {
                    const values = manifest[field] || [];
                    const filtered = values.filter(id => !deletedIds.has(id));
                    if (filtered.length === values.length) return manifest;
                    changed = true;
                    return { ...manifest, [field]: filtered };
                });
            }

            if (changed) {
                run.updatedAt = Date.now();
                await RegionPopulationRunStore.set(scene, run);
            }
        }
    }

    static #enqueueChronicleRename(scene, name) {
        if (!scene?.id || !RegionChronicleService.getEntry(scene)) return;
        this.#pending = this.#pending
            .then(() => RegionChronicleService.rename(scene, name))
            .catch(error => console.warn("Augur: Nexus | Failed to rename a regional chronicle.", error));
    }
    static #enqueueChronicle(document) {
        const sceneId = String(document?.flags?.["augur-nexus"]?.sceneId || "").trim();
        if (!sceneId) return;
        this.#pending = this.#pending
            .then(async () => {
                const scene = game.scenes.get(sceneId);
                if (scene) await RegionPopulationRunStore.removeChronicleReference(scene, document.id);
                RegionChronicleService.notifyExternalDeletion(document);
            })
            .catch(error => console.warn("Augur: Nexus | Failed to reconcile a deleted regional chronicle.", error));
    }
    static #enqueue(locationId) {
        const id = String(locationId || "").trim();
        if (!id) return;
        this.#pending = this.#pending
            .then(() => this.#removeOwnedProjections(id))
            .catch(error => console.warn("Augur: Nexus | Failed to clean generated Location projections.", error));
    }

    static #enqueueEntity(entityType, entityId) {
        const id = String(entityId || "").trim();
        if (!id) return;
        this.#pending = this.#pending
            .then(async () => {
                for (const scene of game.scenes?.contents || []) {
                    await RegionPopulationRunStore.removeEntity(scene, entityType, id);
                }
            })
            .catch(error => console.warn("Augur: Nexus | Failed to reconcile a deleted generated entity.", error));
    }

    static async #removeOwnedProjections(locationId) {
        for (const scene of game.scenes?.contents || []) {
            const run = RegionPopulationRunStore.get(scene);
            const manifests = run?.runs?.length ? run.runs : run ? [run] : [];
            if (!manifests.some(manifest => manifest.locationIds?.includes(locationId))) continue;
            const provider = RegionProviderRegistry.get(run.regionProviderId);
            if (typeof provider?.removeCellOverlaysForOwner === "function") {
                for (const manifest of manifests) {
                    if (manifest.overlayContributionId) {
                        await provider.removeCellOverlaysForOwner(scene, manifest.overlayContributionId, locationId);
                    }
                }
            }
            await RegionPopulationRunStore.removeLocation(scene, locationId);
        }
    }
}
